PRE-MERGE #16513 Add ability to save input action from command line

This commit is contained in:
Mike Griese
2024-06-03 14:58:40 -05:00
19 changed files with 358 additions and 33 deletions

View File

@@ -1264,6 +1264,54 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleSaveTask(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (Feature_SaveTask::IsEnabled())
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<SaveTaskArgs>())
{
if (realArgs.Commandline().empty())
{
if (const auto termControl{ _GetActiveControl() })
{
if (termControl.HasSelection())
{
const auto selections{ termControl.SelectedText(true) };
const auto selection = std::accumulate(selections.begin(), selections.end(), std::wstring());
realArgs.Commandline(selection);
}
}
}
try
{
winrt::Microsoft::Terminal::Control::KeyChord keyChord = nullptr;
if (!realArgs.KeyChord().empty())
{
keyChord = KeyChordSerialization::FromString(winrt::to_hstring(realArgs.KeyChord()));
}
_settings.GlobalSettings().ActionMap().AddSendInputAction(realArgs.Name(), realArgs.Commandline(), keyChord);
_settings.WriteSettingsToDisk();
ActionSaved(realArgs.Commandline(), realArgs.Name(), realArgs.KeyChord());
}
catch (const winrt::hresult_error& ex)
{
auto code = ex.code();
auto message = ex.message();
ActionSaveFailed(message);
args.Handled(true);
return;
}
args.Handled(true);
}
}
}
}
void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View File

@@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser()
_buildMovePaneParser();
_buildSwapPaneParser();
_buildFocusPaneParser();
_buildSaveParser();
}
// Method Description:
@@ -537,6 +538,72 @@ void AppCommandlineArgs::_buildFocusPaneParser()
setupSubcommand(_focusPaneShort);
}
void AppCommandlineArgs::_buildSaveParser()
{
_saveCommand = _app.add_subcommand("x-save", RS_A(L"SaveActionDesc"));
auto setupSubcommand = [this](auto* subcommand) {
subcommand->add_option("--name,-n", _saveInputName, RS_A(L"SaveActionArgDesc"));
subcommand->add_option("--keychord,-k", _keyChordOption, RS_A(L"KeyChordArgDesc"));
subcommand->add_option("command,", _commandline, RS_A(L"CmdCommandArgDesc"));
subcommand->positionals_at_end(true);
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
// Build the NewTab action from the values we've parsed on the commandline.
ActionAndArgs saveAction{};
saveAction.Action(ShortcutAction::SaveTask);
// _getNewTerminalArgs MUST be called before parsing any other options,
// as it might clear those options while finding the commandline
SaveTaskArgs args{};
if (!_commandline.empty())
{
std::ostringstream cmdlineBuffer;
for (const auto& arg : _commandline)
{
if (cmdlineBuffer.tellp() != 0)
{
// If there's already something in here, prepend a space
cmdlineBuffer << ' ';
}
if (arg.find(" ") != std::string::npos)
{
cmdlineBuffer << '"' << arg << '"';
}
else
{
cmdlineBuffer << arg;
}
}
args.Commandline(winrt::to_hstring(cmdlineBuffer.str()));
}
if (!_keyChordOption.empty())
{
args.KeyChord(winrt::to_hstring(_keyChordOption));
}
if (!_saveInputName.empty())
{
winrt::hstring hString = winrt::to_hstring(_saveInputName);
args.Name(hString);
}
saveAction.Args(args);
_startupActions.push_back(saveAction);
});
};
setupSubcommand(_saveCommand);
}
// Method Description:
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
// that subcommand to support all the properties in a NewTerminalArgs.
@@ -710,7 +777,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
*_focusPaneCommand ||
*_focusPaneShort ||
*_newPaneShort.subcommand ||
*_newPaneCommand.subcommand);
*_newPaneCommand.subcommand ||
*_saveCommand);
}
// Method Description:

View File

@@ -93,6 +93,7 @@ private:
CLI::App* _swapPaneCommand;
CLI::App* _focusPaneCommand;
CLI::App* _focusPaneShort;
CLI::App* _saveCommand;
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
@@ -123,6 +124,8 @@ private:
bool _focusPrevTab{ false };
int _focusPaneTarget{ -1 };
std::string _saveInputName;
std::string _keyChordOption;
// Are you adding more args here? Make sure to reset them in _resetStateToDefault
const Commandline* _currentCommandline{ nullptr };
@@ -141,6 +144,7 @@ private:
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
void _addNewTerminalArgs(NewTerminalSubcommand& subcommand);
void _buildParser();
void _buildSaveParser();
void _buildNewTabParser();
void _buildSplitPaneParser();
void _buildFocusTabParser();

View File

@@ -288,6 +288,15 @@
<data name="CmdCommandArgDesc" xml:space="preserve">
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
</data>
<data name="SaveActionDesc" xml:space="preserve">
<value>Save command line as input action</value>
</data>
<data name="SaveActionArgDesc" xml:space="preserve">
<value>An optional argument</value>
</data>
<data name="KeyChordArgDesc" xml:space="preserve">
<value>An optional argument</value>
</data>
<data name="CmdFocusTabDesc" xml:space="preserve">
<value>Move focus to another tab</value>
</data>
@@ -898,4 +907,10 @@
<data name="RestartConnectionToolTip" xml:space="preserve">
<value>Restart the active pane connection</value>
</data>
<data name="ActionSavedToast.Title" xml:space="preserve">
<value>Action saved</value>
</data>
<data name="ActionSaveFailedToast.Title" xml:space="preserve">
<value>Action save failed</value>
</data>
</root>

View File

@@ -357,7 +357,9 @@
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>
<PRIResource Include="Resources\en-US\Resources.resw" />
<PRIResource Include="Resources\en-US\Resources.resw">
<SubType>Designer</SubType>
</PRIResource>
<PRIResource Include="Resources\en-US\ContextMenu.resw" />
<OCResourceDirectory Include="Resources" />
</ItemGroup>
@@ -466,10 +468,8 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<!--
By default, the PRI file will contain resource paths beginning with the
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
@@ -490,4 +490,4 @@
</ItemGroup>
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>
</Project>

View File

@@ -4185,6 +4185,66 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::fire_and_forget TerminalPage::ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (auto page{ weakThis.get() })
{
// If we haven't ever loaded the TeachingTip, then do so now and
// create the toast for it.
if (page->_actionSavedToast == nullptr)
{
if (auto tip{ page->FindName(L"ActionSavedToast").try_as<MUX::Controls::TeachingTip>() })
{
page->_actionSavedToast = std::make_shared<Toast>(tip);
// Make sure to use the weak ref when setting up this
// callback.
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
}
}
_UpdateTeachingTipTheme(ActionSavedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
SavedActionName(name);
SavedActionKeyChord(keyChord);
SavedActionCommandLine(input);
if (page->_actionSavedToast != nullptr)
{
page->_actionSavedToast->Open();
}
}
}
winrt::fire_and_forget TerminalPage::ActionSaveFailed(winrt::hstring message)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (auto page{ weakThis.get() })
{
// If we haven't ever loaded the TeachingTip, then do so now and
// create the toast for it.
if (page->_actionSaveFailedToast == nullptr)
{
if (auto tip{ page->FindName(L"ActionSaveFailedToast").try_as<MUX::Controls::TeachingTip>() })
{
page->_actionSaveFailedToast = std::make_shared<Toast>(tip);
// Make sure to use the weak ref when setting up this
// callback.
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
}
}
_UpdateTeachingTipTheme(ActionSaveFailedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
ActionSaveFailedMessage().Text(message);
if (page->_actionSaveFailedToast != nullptr)
{
page->_actionSaveFailedToast->Open();
}
}
}
// Method Description:
// - Called when an attempt to rename the window has failed. This will open
// the toast displaying a message to the user that the attempt to rename

View File

@@ -146,6 +146,8 @@ namespace winrt::TerminalApp::implementation
winrt::hstring KeyboardServiceDisabledText();
winrt::fire_and_forget IdentifyWindow();
winrt::fire_and_forget ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord);
winrt::fire_and_forget ActionSaveFailed(winrt::hstring message);
winrt::fire_and_forget RenameFailed();
winrt::fire_and_forget ShowTerminalWorkingDirectory();
@@ -199,6 +201,10 @@ namespace winrt::TerminalApp::implementation
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionName, PropertyChanged.raise, L"");
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionKeyChord, PropertyChanged.raise, L"");
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionCommandLine, PropertyChanged.raise, L"");
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
std::optional<HWND> _hostingHwnd;
@@ -258,6 +264,8 @@ namespace winrt::TerminalApp::implementation
bool _isEmbeddingInboundListener{ false };
std::shared_ptr<Toast> _windowIdToast{ nullptr };
std::shared_ptr<Toast> _actionSavedToast{ nullptr };
std::shared_ptr<Toast> _actionSaveFailedToast{ nullptr };
std::shared_ptr<Toast> _windowRenameFailedToast{ nullptr };
std::shared_ptr<Toast> _windowCwdToast{ nullptr };

View File

@@ -72,6 +72,10 @@ namespace TerminalApp
void IdentifyWindow();
void RenameFailed();
String SavedActionName { get; };
String SavedActionKeyChord { get; };
String SavedActionCommandLine { get; };
// We cannot use the default XAML APIs because we want to make sure
// that there's only one application-global dialog visible at a time,
// and because of GH#5224.

View File

@@ -8,6 +8,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
Background="Transparent"
mc:Ignorable="d">
@@ -204,5 +205,43 @@
Title="{x:Bind WindowProperties.VirtualWorkingDirectory, Mode=OneWay}"
x:Load="False"
IsLightDismissEnabled="True" />
<mux:TeachingTip x:Name="ActionSavedToast"
x:Uid="ActionSavedToast"
Title="Action Saved"
HorizontalAlignment="Stretch"
x:Load="False"
IsLightDismissEnabled="True">
<mux:TeachingTip.Content>
<StackPanel HorizontalAlignment="Stretch"
Orientation="Vertical">
<TextBlock x:Name="ActionSavedNameText"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionName), Mode=OneWay}">
<Run Text="Name: " />
<Run Text="{x:Bind SavedActionName, Mode=OneWay}" />
</TextBlock>
<TextBlock x:Name="ActionSavedKeyChordText"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionKeyChord), Mode=OneWay}">
<Run Text="Key Chord: " />
<Run Text="{x:Bind SavedActionKeyChord, Mode=OneWay}" />
</TextBlock>
<TextBlock x:Name="ActionSavedCommandLineText"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionCommandLine), Mode=OneWay}">
<Run Text="Input: " />
<Run Text="{x:Bind SavedActionCommandLine, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</mux:TeachingTip.Content>
</mux:TeachingTip>
<mux:TeachingTip x:Name="ActionSaveFailedToast"
x:Uid="ActionSaveFailedToast"
Title="Action Save Failed"
x:Load="False"
IsLightDismissEnabled="True">
<mux:TeachingTip.Content>
<TextBlock x:Name="ActionSaveFailedMessage"
Text="" />
</mux:TeachingTip.Content>
</mux:TeachingTip>
</Grid>
</Page>

View File

@@ -52,6 +52,7 @@ static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
static constexpr std::string_view TabSearchKey{ "tabSearch" };
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
static constexpr std::string_view SaveTaskKey{ "experimental.saveTask" };
static constexpr std::string_view SuggestionsKey{ "showSuggestions" };
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
static constexpr std::string_view SetFocusModeKey{ "setFocusMode" };
@@ -389,6 +390,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") },
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
{ ShortcutAction::ToggleCommandPalette, MustGenerate },
{ ShortcutAction::SaveTask, MustGenerate },
{ ShortcutAction::Suggestions, MustGenerate },
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
{ ShortcutAction::SetFocusMode, MustGenerate },

View File

@@ -33,6 +33,7 @@
#include "ScrollToMarkArgs.g.cpp"
#include "AddMarkArgs.g.cpp"
#include "FindMatchArgs.g.cpp"
#include "SaveTaskArgs.g.cpp"
#include "ToggleCommandPaletteArgs.g.cpp"
#include "SuggestionsArgs.g.cpp"
#include "NewWindowArgs.g.cpp"
@@ -947,6 +948,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}
winrt::hstring SaveTaskArgs::GenerateName() const
{
if (Feature_SaveTask::IsEnabled())
{
std::wstringstream ss;
ss << RS_(L"SaveActionNamePrefix").c_str() << L" commandline: " << Commandline().c_str();
if (!Name().empty())
{
ss << L", name: " << Name().c_str();
}
if (!KeyChord().empty())
{
ss << L", keyChord " << KeyChord().c_str();
}
return winrt::hstring{ ss.str() };
}
return L"";
}
static winrt::hstring _FormatColorString(const Control::SelectionColor& selectionColor)
{
if (!selectionColor)

View File

@@ -34,6 +34,7 @@
#include "ScrollToMarkArgs.g.h"
#include "AddMarkArgs.g.h"
#include "MoveTabArgs.g.h"
#include "SaveTaskArgs.g.h"
#include "ToggleCommandPaletteArgs.g.h"
#include "SuggestionsArgs.g.h"
#include "FindMatchArgs.g.h"
@@ -215,6 +216,12 @@ protected: \
#define TOGGLE_COMMAND_PALETTE_ARGS(X) \
X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, CommandPaletteLaunchMode::Action)
////////////////////////////////////////////////////////////////////////////////
#define SAVE_TASK_ARGS(X) \
X(winrt::hstring, Name, "name", false, L"") \
X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), L"") \
X(winrt::hstring, KeyChord, "keyChord", false, L"")
////////////////////////////////////////////////////////////////////////////////
#define SUGGESTIONS_ARGS(X) \
X(SuggestionsSource, Source, "source", false, SuggestionsSource::Tasks) \
@@ -819,6 +826,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ACTION_ARGS_STRUCT(ToggleCommandPaletteArgs, TOGGLE_COMMAND_PALETTE_ARGS);
ACTION_ARGS_STRUCT(SaveTaskArgs, SAVE_TASK_ARGS);
ACTION_ARGS_STRUCT(SuggestionsArgs, SUGGESTIONS_ARGS);
ACTION_ARGS_STRUCT(FindMatchArgs, FIND_MATCH_ARGS);
@@ -940,6 +949,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(CloseTabArgs);
BASIC_FACTORY(MoveTabArgs);
BASIC_FACTORY(OpenSettingsArgs);
BASIC_FACTORY(SaveTaskArgs);
BASIC_FACTORY(FindMatchArgs);
BASIC_FACTORY(NewWindowArgs);
BASIC_FACTORY(FocusPaneArgs);

View File

@@ -354,6 +354,15 @@ namespace Microsoft.Terminal.Settings.Model
FindMatchDirection Direction { get; };
};
[default_interface] runtimeclass SaveTaskArgs : IActionArgs
{
SaveTaskArgs();
SaveTaskArgs(String Name, String Commandline, String KeyChord);
String Name;
String Commandline;
String KeyChord;
};
[default_interface] runtimeclass NewWindowArgs : IActionArgs
{
NewWindowArgs(INewContentArgs contentArgs);

View File

@@ -755,6 +755,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
AddAction(*cmd, keys);
}
void ActionMap::AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys)
{
auto newAction = winrt::make<ActionAndArgs>();
newAction.Action(ShortcutAction::SendInput);
auto sendInputArgs = winrt::make<SendInputArgs>(input);
newAction.Args(sendInputArgs);
auto cmd{ make_self<Command>() };
cmd->ActionAndArgs(newAction);
if (!name.empty())
{
cmd->Name(name);
}
cmd->GenerateID();
AddAction(*cmd, keys);
}
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
static bool _compareSchemeNames(const ColorScheme& lhs, const ColorScheme& rhs)
{

View File

@@ -78,6 +78,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys);
void DeleteKeyBinding(const Control::KeyChord& keys);
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
void AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys);
Windows::Foundation::Collections::IVector<Model::Command> ExpandedCommands();
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,

View File

@@ -31,5 +31,6 @@ namespace Microsoft.Terminal.Settings.Model
void DeleteKeyBinding(Microsoft.Terminal.Control.KeyChord keys);
void RegisterKeyBinding(Microsoft.Terminal.Control.KeyChord keys, ActionAndArgs action);
void AddSendInputAction(String name, String input, Microsoft.Terminal.Control.KeyChord keys);
}
}

View File

@@ -75,6 +75,7 @@
ON_ALL_ACTIONS(CloseTabsAfter) \
ON_ALL_ACTIONS(TabSearch) \
ON_ALL_ACTIONS(MoveTab) \
ON_ALL_ACTIONS(SaveTask) \
ON_ALL_ACTIONS(BreakIntoDebugger) \
ON_ALL_ACTIONS(TogglePaneReadOnly) \
ON_ALL_ACTIONS(EnablePaneReadOnly) \
@@ -148,6 +149,7 @@
ON_ALL_ACTIONS_WITH_ARGS(SplitPane) \
ON_ALL_ACTIONS_WITH_ARGS(SwitchToTab) \
ON_ALL_ACTIONS_WITH_ARGS(ToggleCommandPalette) \
ON_ALL_ACTIONS_WITH_ARGS(SaveTask) \
ON_ALL_ACTIONS_WITH_ARGS(FocusPane) \
ON_ALL_ACTIONS_WITH_ARGS(ExportBuffer) \
ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer) \

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -727,4 +727,7 @@
<value>Open about dialog</value>
<comment>This will open the "about" dialog, to display version info and other documentation</comment>
</data>
</root>
<data name="SaveActionNamePrefix" xml:space="preserve">
<value>Save Task</value>
</data>
</root>

View File

@@ -155,4 +155,15 @@
</alwaysEnabledBrandingTokens>
</feature>
<feature>
<name>Feature_SaveTask</name>
<description>Save Input</description>
<id>9971</id>
<stage>AlwaysDisabled</stage>
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
<brandingToken>Canary</brandingToken>
</alwaysEnabledBrandingTokens>
</feature>
</featureStaging>