mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
Desktop: Simplified and improve command service, and added command palette
- Commands "enabled" state is now expressed using a "when-clause" like in VSCode - A command palette has been added to the Tools menu
This commit is contained in:
parent
f529adac99
commit
3a57cfea02
@ -114,7 +114,7 @@ ElectronClient/gui/MainScreen/commands/showNoteProperties.js
|
|||||||
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleSidebar.js
|
ElectronClient/gui/MainScreen/commands/toggleSideBar.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||||
ElectronClient/gui/MainScreen/MainScreen.js
|
ElectronClient/gui/MainScreen/MainScreen.js
|
||||||
ElectronClient/gui/MenuBar.js
|
ElectronClient/gui/MenuBar.js
|
||||||
@ -187,7 +187,9 @@ ElectronClient/gui/ToolbarButton/styles/index.js
|
|||||||
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
||||||
ElectronClient/gui/utils/NoteListUtils.js
|
ElectronClient/gui/utils/NoteListUtils.js
|
||||||
ElectronClient/InteropServiceHelper.js
|
ElectronClient/InteropServiceHelper.js
|
||||||
|
ElectronClient/plugins/GotoAnything.js
|
||||||
ElectronClient/services/bridge.js
|
ElectronClient/services/bridge.js
|
||||||
|
ElectronClient/services/commands/types.js
|
||||||
ElectronClient/services/plugins/hooks/useThemeCss.js
|
ElectronClient/services/plugins/hooks/useThemeCss.js
|
||||||
ElectronClient/services/plugins/hooks/useViewIsReady.js
|
ElectronClient/services/plugins/hooks/useViewIsReady.js
|
||||||
ElectronClient/services/plugins/PlatformImplementation.js
|
ElectronClient/services/plugins/PlatformImplementation.js
|
||||||
@ -235,9 +237,10 @@ ReactNativeClient/lib/services/AlarmServiceDriver.android.js
|
|||||||
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
|
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
|
||||||
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
|
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
|
||||||
ReactNativeClient/lib/services/BaseService.js
|
ReactNativeClient/lib/services/BaseService.js
|
||||||
ReactNativeClient/lib/services/BooleanExpression.js
|
ReactNativeClient/lib/services/commands/commandsToMarkdownTable.js
|
||||||
ReactNativeClient/lib/services/commands/MenuUtils.js
|
ReactNativeClient/lib/services/commands/MenuUtils.js
|
||||||
ReactNativeClient/lib/services/commands/propsHaveChanged.js
|
ReactNativeClient/lib/services/commands/propsHaveChanged.js
|
||||||
|
ReactNativeClient/lib/services/commands/stateToWhenClauseContext.js
|
||||||
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
|
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
|
||||||
ReactNativeClient/lib/services/CommandService.js
|
ReactNativeClient/lib/services/CommandService.js
|
||||||
ReactNativeClient/lib/services/contextkey/contextkey.js
|
ReactNativeClient/lib/services/contextkey/contextkey.js
|
||||||
@ -309,6 +312,7 @@ ReactNativeClient/lib/services/synchronizer/migrations/1.js
|
|||||||
ReactNativeClient/lib/services/synchronizer/migrations/2.js
|
ReactNativeClient/lib/services/synchronizer/migrations/2.js
|
||||||
ReactNativeClient/lib/services/synchronizer/utils/types.js
|
ReactNativeClient/lib/services/synchronizer/utils/types.js
|
||||||
ReactNativeClient/lib/services/UndoRedoService.js
|
ReactNativeClient/lib/services/UndoRedoService.js
|
||||||
|
ReactNativeClient/lib/services/WhenClause.js
|
||||||
ReactNativeClient/lib/ShareExtension.js
|
ReactNativeClient/lib/ShareExtension.js
|
||||||
ReactNativeClient/lib/shareHandler.js
|
ReactNativeClient/lib/shareHandler.js
|
||||||
ReactNativeClient/lib/shim.js
|
ReactNativeClient/lib/shim.js
|
||||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -108,7 +108,7 @@ ElectronClient/gui/MainScreen/commands/showNoteProperties.js
|
|||||||
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleSidebar.js
|
ElectronClient/gui/MainScreen/commands/toggleSideBar.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||||
ElectronClient/gui/MainScreen/MainScreen.js
|
ElectronClient/gui/MainScreen/MainScreen.js
|
||||||
ElectronClient/gui/MenuBar.js
|
ElectronClient/gui/MenuBar.js
|
||||||
@ -181,7 +181,9 @@ ElectronClient/gui/ToolbarButton/styles/index.js
|
|||||||
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
||||||
ElectronClient/gui/utils/NoteListUtils.js
|
ElectronClient/gui/utils/NoteListUtils.js
|
||||||
ElectronClient/InteropServiceHelper.js
|
ElectronClient/InteropServiceHelper.js
|
||||||
|
ElectronClient/plugins/GotoAnything.js
|
||||||
ElectronClient/services/bridge.js
|
ElectronClient/services/bridge.js
|
||||||
|
ElectronClient/services/commands/types.js
|
||||||
ElectronClient/services/plugins/hooks/useThemeCss.js
|
ElectronClient/services/plugins/hooks/useThemeCss.js
|
||||||
ElectronClient/services/plugins/hooks/useViewIsReady.js
|
ElectronClient/services/plugins/hooks/useViewIsReady.js
|
||||||
ElectronClient/services/plugins/PlatformImplementation.js
|
ElectronClient/services/plugins/PlatformImplementation.js
|
||||||
@ -229,9 +231,10 @@ ReactNativeClient/lib/services/AlarmServiceDriver.android.js
|
|||||||
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
|
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
|
||||||
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
|
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
|
||||||
ReactNativeClient/lib/services/BaseService.js
|
ReactNativeClient/lib/services/BaseService.js
|
||||||
ReactNativeClient/lib/services/BooleanExpression.js
|
ReactNativeClient/lib/services/commands/commandsToMarkdownTable.js
|
||||||
ReactNativeClient/lib/services/commands/MenuUtils.js
|
ReactNativeClient/lib/services/commands/MenuUtils.js
|
||||||
ReactNativeClient/lib/services/commands/propsHaveChanged.js
|
ReactNativeClient/lib/services/commands/propsHaveChanged.js
|
||||||
|
ReactNativeClient/lib/services/commands/stateToWhenClauseContext.js
|
||||||
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
|
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
|
||||||
ReactNativeClient/lib/services/CommandService.js
|
ReactNativeClient/lib/services/CommandService.js
|
||||||
ReactNativeClient/lib/services/contextkey/contextkey.js
|
ReactNativeClient/lib/services/contextkey/contextkey.js
|
||||||
@ -303,6 +306,7 @@ ReactNativeClient/lib/services/synchronizer/migrations/1.js
|
|||||||
ReactNativeClient/lib/services/synchronizer/migrations/2.js
|
ReactNativeClient/lib/services/synchronizer/migrations/2.js
|
||||||
ReactNativeClient/lib/services/synchronizer/utils/types.js
|
ReactNativeClient/lib/services/synchronizer/utils/types.js
|
||||||
ReactNativeClient/lib/services/UndoRedoService.js
|
ReactNativeClient/lib/services/UndoRedoService.js
|
||||||
|
ReactNativeClient/lib/services/WhenClause.js
|
||||||
ReactNativeClient/lib/ShareExtension.js
|
ReactNativeClient/lib/ShareExtension.js
|
||||||
ReactNativeClient/lib/shareHandler.js
|
ReactNativeClient/lib/shareHandler.js
|
||||||
ReactNativeClient/lib/shim.js
|
ReactNativeClient/lib/shim.js
|
||||||
|
8
.ignore
8
.ignore
@ -57,7 +57,7 @@ ElectronClient/gui/MainScreen/commands/showNoteProperties.js
|
|||||||
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleSidebar.js
|
ElectronClient/gui/MainScreen/commands/toggleSideBar.js
|
||||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||||
ElectronClient/gui/MainScreen/MainScreen.js
|
ElectronClient/gui/MainScreen/MainScreen.js
|
||||||
ElectronClient/gui/MenuBar.js
|
ElectronClient/gui/MenuBar.js
|
||||||
@ -130,7 +130,9 @@ ElectronClient/gui/ToolbarButton/styles/index.js
|
|||||||
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
||||||
ElectronClient/gui/utils/NoteListUtils.js
|
ElectronClient/gui/utils/NoteListUtils.js
|
||||||
ElectronClient/InteropServiceHelper.js
|
ElectronClient/InteropServiceHelper.js
|
||||||
|
ElectronClient/plugins/GotoAnything.js
|
||||||
ElectronClient/services/bridge.js
|
ElectronClient/services/bridge.js
|
||||||
|
ElectronClient/services/commands/types.js
|
||||||
ElectronClient/services/plugins/hooks/useThemeCss.js
|
ElectronClient/services/plugins/hooks/useThemeCss.js
|
||||||
ElectronClient/services/plugins/hooks/useViewIsReady.js
|
ElectronClient/services/plugins/hooks/useViewIsReady.js
|
||||||
ElectronClient/services/plugins/PlatformImplementation.js
|
ElectronClient/services/plugins/PlatformImplementation.js
|
||||||
@ -178,9 +180,10 @@ ReactNativeClient/lib/services/AlarmServiceDriver.android.js
|
|||||||
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
|
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
|
||||||
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
|
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
|
||||||
ReactNativeClient/lib/services/BaseService.js
|
ReactNativeClient/lib/services/BaseService.js
|
||||||
ReactNativeClient/lib/services/BooleanExpression.js
|
ReactNativeClient/lib/services/commands/commandsToMarkdownTable.js
|
||||||
ReactNativeClient/lib/services/commands/MenuUtils.js
|
ReactNativeClient/lib/services/commands/MenuUtils.js
|
||||||
ReactNativeClient/lib/services/commands/propsHaveChanged.js
|
ReactNativeClient/lib/services/commands/propsHaveChanged.js
|
||||||
|
ReactNativeClient/lib/services/commands/stateToWhenClauseContext.js
|
||||||
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
|
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
|
||||||
ReactNativeClient/lib/services/CommandService.js
|
ReactNativeClient/lib/services/CommandService.js
|
||||||
ReactNativeClient/lib/services/contextkey/contextkey.js
|
ReactNativeClient/lib/services/contextkey/contextkey.js
|
||||||
@ -252,6 +255,7 @@ ReactNativeClient/lib/services/synchronizer/migrations/1.js
|
|||||||
ReactNativeClient/lib/services/synchronizer/migrations/2.js
|
ReactNativeClient/lib/services/synchronizer/migrations/2.js
|
||||||
ReactNativeClient/lib/services/synchronizer/utils/types.js
|
ReactNativeClient/lib/services/synchronizer/utils/types.js
|
||||||
ReactNativeClient/lib/services/UndoRedoService.js
|
ReactNativeClient/lib/services/UndoRedoService.js
|
||||||
|
ReactNativeClient/lib/services/WhenClause.js
|
||||||
ReactNativeClient/lib/ShareExtension.js
|
ReactNativeClient/lib/ShareExtension.js
|
||||||
ReactNativeClient/lib/shareHandler.js
|
ReactNativeClient/lib/shareHandler.js
|
||||||
ReactNativeClient/lib/shim.js
|
ReactNativeClient/lib/shim.js
|
||||||
|
@ -2,7 +2,7 @@ import MenuUtils from 'lib/services/commands/MenuUtils';
|
|||||||
import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
|
import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
|
||||||
import CommandService, { CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
import CommandService, { CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
||||||
|
|
||||||
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
|
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow } = require('test-utils.js');
|
||||||
|
|
||||||
interface TestCommand {
|
interface TestCommand {
|
||||||
declaration: CommandDeclaration,
|
declaration: CommandDeclaration,
|
||||||
@ -11,7 +11,12 @@ interface TestCommand {
|
|||||||
|
|
||||||
function newService():CommandService {
|
function newService():CommandService {
|
||||||
const service = new CommandService();
|
const service = new CommandService();
|
||||||
service.initialize({});
|
const mockStore = {
|
||||||
|
getState: () => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
service.initialize(mockStore, true);
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,8 +29,7 @@ function createCommand(name:string, options:any):TestCommand {
|
|||||||
execute: options.execute,
|
execute: options.execute,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.mapStateToProps) runtime.mapStateToProps = options.mapStateToProps;
|
if (options.enabledCondition) runtime.enabledCondition = options.enabledCondition;
|
||||||
if (options.isEnabled) runtime.isEnabled = options.isEnabled;
|
|
||||||
|
|
||||||
return { declaration, runtime };
|
return { declaration, runtime };
|
||||||
}
|
}
|
||||||
@ -61,7 +65,7 @@ describe('services_CommandService', function() {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const toolbarInfos = toolbarButtonUtils.commandsToToolbarButtons({}, ['test1', 'test2']);
|
const toolbarInfos = toolbarButtonUtils.commandsToToolbarButtons(['test1', 'test2'], {});
|
||||||
|
|
||||||
await toolbarInfos[0].onClick();
|
await toolbarInfos[0].onClick();
|
||||||
await toolbarInfos[1].onClick();
|
await toolbarInfos[1].onClick();
|
||||||
@ -77,98 +81,78 @@ describe('services_CommandService', function() {
|
|||||||
|
|
||||||
registerCommand(service, createCommand('test1', {
|
registerCommand(service, createCommand('test1', {
|
||||||
execute: () => {},
|
execute: () => {},
|
||||||
mapStateToProps: (state:any) => {
|
enabledCondition: 'oneNoteSelected',
|
||||||
return {
|
|
||||||
selectedNoteId: state.selectedNoteId,
|
|
||||||
selectedFolderId: state.selectedFolderId,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
isEnabled: (props:any) => {
|
|
||||||
return props.selectedNoteId === 'abc';
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
registerCommand(service, createCommand('test2', {
|
registerCommand(service, createCommand('test2', {
|
||||||
execute: () => {},
|
execute: () => {},
|
||||||
mapStateToProps: (state:any) => {
|
enabledCondition: 'multipleNotesSelected',
|
||||||
return {
|
|
||||||
selectedNoteId: state.selectedNoteId,
|
|
||||||
selectedFolderId: state.selectedFolderId,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
isEnabled: (props:any) => {
|
|
||||||
return props.selectedNoteId === '123';
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const toolbarInfos = toolbarButtonUtils.commandsToToolbarButtons({
|
const toolbarInfos = toolbarButtonUtils.commandsToToolbarButtons(['test1', 'test2'], {
|
||||||
selectedNoteId: '123',
|
oneNoteSelected: false,
|
||||||
selectedFolderId: 'aaa',
|
multipleNotesSelected: true,
|
||||||
}, ['test1', 'test2']);
|
});
|
||||||
|
|
||||||
expect(toolbarInfos[0].enabled).toBe(false);
|
expect(toolbarInfos[0].enabled).toBe(false);
|
||||||
expect(toolbarInfos[1].enabled).toBe(true);
|
expect(toolbarInfos[1].enabled).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should enable commands by default', asyncTest(async () => {
|
||||||
|
const service = newService();
|
||||||
|
|
||||||
|
registerCommand(service, createCommand('test1', {
|
||||||
|
execute: () => {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(service.isEnabled('test1', {})).toBe(true);
|
||||||
|
}));
|
||||||
|
|
||||||
it('should return the same toolbarButtons array if nothing has changed', asyncTest(async () => {
|
it('should return the same toolbarButtons array if nothing has changed', asyncTest(async () => {
|
||||||
const service = newService();
|
const service = newService();
|
||||||
const toolbarButtonUtils = new ToolbarButtonUtils(service);
|
const toolbarButtonUtils = new ToolbarButtonUtils(service);
|
||||||
|
|
||||||
registerCommand(service, createCommand('test1', {
|
registerCommand(service, createCommand('test1', {
|
||||||
execute: () => {},
|
execute: () => {},
|
||||||
mapStateToProps: (state:any) => {
|
enabledCondition: 'cond1',
|
||||||
return {
|
|
||||||
selectedNoteId: state.selectedNoteId,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
isEnabled: (props:any) => {
|
|
||||||
return props.selectedNoteId === 'ok';
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
registerCommand(service, createCommand('test2', {
|
registerCommand(service, createCommand('test2', {
|
||||||
execute: () => {},
|
execute: () => {},
|
||||||
mapStateToProps: (state:any) => {
|
enabledCondition: 'cond2',
|
||||||
return {
|
|
||||||
selectedFolderId: state.selectedFolderId,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
isEnabled: (props:any) => {
|
|
||||||
return props.selectedFolderId === 'ok';
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const toolbarInfos1 = toolbarButtonUtils.commandsToToolbarButtons({
|
const toolbarInfos1 = toolbarButtonUtils.commandsToToolbarButtons(['test1', 'test2'], {
|
||||||
selectedNoteId: 'ok',
|
cond1: true,
|
||||||
selectedFolderId: 'notok',
|
cond2: false,
|
||||||
}, ['test1', 'test2']);
|
});
|
||||||
|
|
||||||
const toolbarInfos2 = toolbarButtonUtils.commandsToToolbarButtons({
|
const toolbarInfos2 = toolbarButtonUtils.commandsToToolbarButtons(['test1', 'test2'], {
|
||||||
selectedNoteId: 'ok',
|
cond1: true,
|
||||||
selectedFolderId: 'notok',
|
cond2: false,
|
||||||
}, ['test1', 'test2']);
|
});
|
||||||
|
|
||||||
expect(toolbarInfos1).toBe(toolbarInfos2);
|
expect(toolbarInfos1).toBe(toolbarInfos2);
|
||||||
expect(toolbarInfos1[0] === toolbarInfos2[0]).toBe(true);
|
expect(toolbarInfos1[0] === toolbarInfos2[0]).toBe(true);
|
||||||
expect(toolbarInfos1[1] === toolbarInfos2[1]).toBe(true);
|
expect(toolbarInfos1[1] === toolbarInfos2[1]).toBe(true);
|
||||||
|
|
||||||
const toolbarInfos3 = toolbarButtonUtils.commandsToToolbarButtons({
|
const toolbarInfos3 = toolbarButtonUtils.commandsToToolbarButtons(['test1', 'test2'], {
|
||||||
selectedNoteId: 'ok',
|
cond1: true,
|
||||||
selectedFolderId: 'ok',
|
cond2: true,
|
||||||
}, ['test1', 'test2']);
|
});
|
||||||
|
|
||||||
expect(toolbarInfos2 === toolbarInfos3).toBe(false);
|
expect(toolbarInfos2 === toolbarInfos3).toBe(false);
|
||||||
expect(toolbarInfos2[0] === toolbarInfos3[0]).toBe(true);
|
expect(toolbarInfos2[0] === toolbarInfos3[0]).toBe(true);
|
||||||
expect(toolbarInfos2[1] === toolbarInfos3[1]).toBe(false);
|
expect(toolbarInfos2[1] === toolbarInfos3[1]).toBe(false);
|
||||||
|
|
||||||
{
|
{
|
||||||
expect(toolbarButtonUtils.commandsToToolbarButtons({
|
expect(toolbarButtonUtils.commandsToToolbarButtons(['test1', '-', 'test2'], {
|
||||||
selectedNoteId: 'ok',
|
cond1: true,
|
||||||
selectedFolderId: 'notok',
|
cond2: false,
|
||||||
}, ['test1', '-', 'test2'])).toBe(toolbarButtonUtils.commandsToToolbarButtons({
|
})).toBe(toolbarButtonUtils.commandsToToolbarButtons(['test1', '-', 'test2'], {
|
||||||
selectedNoteId: 'ok',
|
cond1: true,
|
||||||
selectedFolderId: 'notok',
|
cond2: false,
|
||||||
}, ['test1', '-', 'test2']));
|
}));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -206,50 +190,37 @@ describe('services_CommandService', function() {
|
|||||||
const utils = new MenuUtils(service);
|
const utils = new MenuUtils(service);
|
||||||
|
|
||||||
registerCommand(service, createCommand('test1', {
|
registerCommand(service, createCommand('test1', {
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return {
|
|
||||||
isOk: state.test1 === 'ok',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
execute: () => {},
|
execute: () => {},
|
||||||
|
enabledCondition: 'cond1',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
registerCommand(service, createCommand('test2', {
|
registerCommand(service, createCommand('test2', {
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return {
|
|
||||||
isOk: state.test2 === 'ok',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
execute: () => {},
|
execute: () => {},
|
||||||
|
enabledCondition: 'cond2',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
{
|
{
|
||||||
const menuItemProps = utils.commandsToMenuItemProps({
|
const menuItemProps = utils.commandsToMenuItemProps(['test1', 'test2'], {
|
||||||
test1: 'ok',
|
cond1: true,
|
||||||
test2: 'notok',
|
cond2: false,
|
||||||
}, ['test1', 'test2']);
|
});
|
||||||
|
|
||||||
expect(menuItemProps.test1.isOk).toBe(true);
|
expect(menuItemProps.test1.enabled).toBe(true);
|
||||||
expect(menuItemProps.test2.isOk).toBe(false);
|
expect(menuItemProps.test2.enabled).toBe(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const menuItemProps = utils.commandsToMenuItemProps({
|
const menuItemProps = utils.commandsToMenuItemProps(['test1', 'test2'], {
|
||||||
test1: 'ok',
|
cond1: true,
|
||||||
test2: 'ok',
|
cond2: true,
|
||||||
}, ['test1', 'test2']);
|
});
|
||||||
|
|
||||||
expect(menuItemProps.test1.isOk).toBe(true);
|
expect(menuItemProps.test1.enabled).toBe(true);
|
||||||
expect(menuItemProps.test2.isOk).toBe(true);
|
expect(menuItemProps.test2.enabled).toBe(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(utils.commandsToMenuItemProps({
|
expect(utils.commandsToMenuItemProps(['test1', 'test2'], { cond1: true, cond2: true }))
|
||||||
test1: 'ok',
|
.toBe(utils.commandsToMenuItemProps(['test1', 'test2'], { cond1: true, cond2: true }));
|
||||||
test2: 'ok',
|
|
||||||
}, ['test1', 'test2'])).toBe(utils.commandsToMenuItemProps({
|
|
||||||
test1: 'ok',
|
|
||||||
test2: 'ok',
|
|
||||||
}, ['test1', 'test2']));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create stateful menu items', asyncTest(async () => {
|
it('should create stateful menu items', asyncTest(async () => {
|
||||||
@ -259,20 +230,30 @@ describe('services_CommandService', function() {
|
|||||||
let propValue = null;
|
let propValue = null;
|
||||||
|
|
||||||
registerCommand(service, createCommand('test1', {
|
registerCommand(service, createCommand('test1', {
|
||||||
mapStateToProps: (state:any) => {
|
execute: (_context:any, greeting:string) => {
|
||||||
return {
|
propValue = greeting;
|
||||||
isOk: state.test1 === 'ok',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
execute: (props:any) => {
|
|
||||||
propValue = props.isOk;
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const menuItem = utils.commandToStatefulMenuItem('test1', { isOk: 'hello' });
|
const menuItem = utils.commandToStatefulMenuItem('test1', 'hello');
|
||||||
menuItem.click();
|
menuItem.click();
|
||||||
|
|
||||||
expect(propValue).toBe('hello');
|
expect(propValue).toBe('hello');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should throw an error for invalid when clause keys in dev mode', asyncTest(async () => {
|
||||||
|
const service = newService();
|
||||||
|
|
||||||
|
registerCommand(service, createCommand('test1', {
|
||||||
|
execute: () => {},
|
||||||
|
enabledCondition: 'cond1 && cond2',
|
||||||
|
}));
|
||||||
|
|
||||||
|
await expectThrow(async () => service.isEnabled('test1', {}));
|
||||||
|
await expectThrow(async () => service.isEnabled('test1', { cond1: true }));
|
||||||
|
await expectNotThrow(async () => service.isEnabled('test1', { cond1: true, cond2: true }));
|
||||||
|
await expectNotThrow(async () => service.isEnabled('test1', { cond1: true, cond2: false }));
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -45,7 +45,7 @@ joplin.plugins.register({
|
|||||||
newLines.push(newCells.join(' | '));
|
newLines.push(newCells.join(' | '));
|
||||||
}
|
}
|
||||||
|
|
||||||
await joplin.commands.execute('replaceSelection', { value: newLines.join('\n') });
|
await joplin.commands.execute('replaceSelection', newLines.join('\n'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2420,6 +2420,11 @@
|
|||||||
"path-exists": "^3.0.0"
|
"path-exists": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lodash.toarray": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
|
||||||
|
},
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -2741,6 +2746,14 @@
|
|||||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node-emoji": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==",
|
||||||
|
"requires": {
|
||||||
|
"lodash.toarray": "^4.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-libs-browser": {
|
"node-libs-browser": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
||||||
@ -3429,11 +3442,6 @@
|
|||||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"slug": {
|
|
||||||
"version": "3.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/slug/-/slug-3.3.4.tgz",
|
|
||||||
"integrity": "sha512-VpHbtRCEWmgaZsrZcTsVl/Dhw98lcrOYDO17DNmJCNpppI6s3qJvnNu2Q3D4L84/2bi6vkW40mjNQI9oGQsflg=="
|
|
||||||
},
|
|
||||||
"snapdragon": {
|
"snapdragon": {
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||||
@ -3981,6 +3989,11 @@
|
|||||||
"imurmurhash": "^0.1.4"
|
"imurmurhash": "^0.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"unorm": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA=="
|
||||||
|
},
|
||||||
"unset-value": {
|
"unset-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||||
@ -4067,6 +4080,14 @@
|
|||||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"uslug": {
|
||||||
|
"version": "git+https://github.com/laurent22/uslug.git#ba2834d79beb0435318709958b2f5e817d96674d",
|
||||||
|
"from": "git+https://github.com/laurent22/uslug.git#emoji-support",
|
||||||
|
"requires": {
|
||||||
|
"node-emoji": "^1.10.0",
|
||||||
|
"unorm": ">= 1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"version": "0.11.1",
|
"version": "0.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||||
|
@ -20,6 +20,6 @@
|
|||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slug": "^3.3.4"
|
"uslug": "git+https://github.com/laurent22/uslug.git#emoji-support"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,9 +49,7 @@ joplin.plugins.register({
|
|||||||
|
|
||||||
panels.onMessage(view, (message:any) => {
|
panels.onMessage(view, (message:any) => {
|
||||||
if (message.name === 'scrollToHash') {
|
if (message.name === 'scrollToHash') {
|
||||||
joplin.commands.execute('scrollToHash', {
|
joplin.commands.execute('scrollToHash', message.hash)
|
||||||
hash: message.hash,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ export default class InteropServiceHelper {
|
|||||||
|
|
||||||
if (Array.isArray(path)) path = path[0];
|
if (Array.isArray(path)) path = path[0];
|
||||||
|
|
||||||
CommandService.instance().execute('showModalMessage', { message: _('Exporting to "%s" as "%s" format. Please wait...', path, module.format) });
|
CommandService.instance().execute('showModalMessage', _('Exporting to "%s" as "%s" format. Please wait...', path, module.format));
|
||||||
|
|
||||||
const exportOptions:ExportOptions = {};
|
const exportOptions:ExportOptions = {};
|
||||||
exportOptions.path = path;
|
exportOptions.path = path;
|
||||||
|
@ -58,7 +58,7 @@ const commands = [
|
|||||||
require('./gui/MainScreen/commands/showNoteProperties'),
|
require('./gui/MainScreen/commands/showNoteProperties'),
|
||||||
require('./gui/MainScreen/commands/showShareNoteDialog'),
|
require('./gui/MainScreen/commands/showShareNoteDialog'),
|
||||||
require('./gui/MainScreen/commands/toggleNoteList'),
|
require('./gui/MainScreen/commands/toggleNoteList'),
|
||||||
require('./gui/MainScreen/commands/toggleSidebar'),
|
require('./gui/MainScreen/commands/toggleSideBar'),
|
||||||
require('./gui/MainScreen/commands/toggleVisiblePanes'),
|
require('./gui/MainScreen/commands/toggleVisiblePanes'),
|
||||||
require('./gui/MainScreen/commands/toggleEditors'),
|
require('./gui/MainScreen/commands/toggleEditors'),
|
||||||
require('./gui/NoteEditor/commands/focusElementNoteBody'),
|
require('./gui/NoteEditor/commands/focusElementNoteBody'),
|
||||||
@ -85,7 +85,7 @@ const globalCommands = [
|
|||||||
const editorCommandDeclarations = require('./gui/NoteEditor/commands/editorCommandDeclarations').default;
|
const editorCommandDeclarations = require('./gui/NoteEditor/commands/editorCommandDeclarations').default;
|
||||||
|
|
||||||
const pluginClasses = [
|
const pluginClasses = [
|
||||||
require('./plugins/GotoAnything.min'),
|
require('./plugins/GotoAnything').default,
|
||||||
];
|
];
|
||||||
|
|
||||||
interface AppStateRoute {
|
interface AppStateRoute {
|
||||||
@ -513,7 +513,7 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
this.initRedux();
|
this.initRedux();
|
||||||
|
|
||||||
CommandService.instance().initialize(this.store());
|
CommandService.instance().initialize(this.store(), Setting.value('env') == 'dev');
|
||||||
|
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
CommandService.instance().registerDeclaration(command.declaration);
|
CommandService.instance().registerDeclaration(command.declaration);
|
||||||
|
@ -6,7 +6,7 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ target }:any) => {
|
execute: async (_context:any, target:string) => {
|
||||||
if (target === 'noteBody') return CommandService.instance().execute('focusElementNoteBody');
|
if (target === 'noteBody') return CommandService.instance().execute('focusElementNoteBody');
|
||||||
if (target === 'noteList') return CommandService.instance().execute('focusElementNoteList');
|
if (target === 'noteList') return CommandService.instance().execute('focusElementNoteList');
|
||||||
if (target === 'sideBar') return CommandService.instance().execute('focusElementSideBar');
|
if (target === 'sideBar') return CommandService.instance().execute('focusElementSideBar');
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import { stateUtils } from 'lib/reducer';
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
|
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
const bridge = require('electron').remote.require('./bridge').default;
|
||||||
|
|
||||||
interface Props {
|
|
||||||
noteId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'startExternalEditing',
|
name: 'startExternalEditing',
|
||||||
label: () => _('Edit in external editor'),
|
label: () => _('Edit in external editor'),
|
||||||
@ -16,21 +13,16 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async (props:Props) => {
|
execute: async (context:CommandContext, noteId:string = null) => {
|
||||||
|
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const note = await Note.load(props.noteId);
|
const note = await Note.load(noteId);
|
||||||
ExternalEditWatcher.instance().openAndWatch(note);
|
ExternalEditWatcher.instance().openAndWatch(note);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
bridge().showErrorMessageBox(_('Error opening note in editor: %s', error.message));
|
bridge().showErrorMessageBox(_('Error opening note in editor: %s', error.message));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any) => {
|
enabledCondition: 'oneNoteSelected',
|
||||||
return !!props.noteId;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return {
|
|
||||||
noteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import { stateUtils } from 'lib/reducer';
|
||||||
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
|
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
|
||||||
|
|
||||||
interface Props {
|
|
||||||
noteId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'stopExternalEditing',
|
name: 'stopExternalEditing',
|
||||||
label: () => _('Stop external editing'),
|
label: () => _('Stop external editing'),
|
||||||
@ -14,14 +11,10 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async (props:Props) => {
|
execute: async (context:CommandContext, noteId:string = null) => {
|
||||||
ExternalEditWatcher.instance().stopWatching(props.noteId);
|
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||||
},
|
ExternalEditWatcher.instance().stopWatching(noteId);
|
||||||
isEnabled: (props:any) => {
|
|
||||||
return !!props.noteId;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return { noteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null };
|
|
||||||
},
|
},
|
||||||
|
enabledCondition: 'oneNoteSelected',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../lib/services/CommandService';
|
import CommandService, { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
import { AppState } from '../app';
|
import { stateUtils } from 'lib/reducer';
|
||||||
import CommandService from 'lib/services/CommandService';
|
import { DesktopCommandContext } from '../services/commands/types';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
noteId: string
|
|
||||||
noteIsBeingWatched: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'toggleExternalEditing',
|
name: 'toggleExternalEditing',
|
||||||
@ -16,27 +11,21 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async (props:Props) => {
|
execute: async (context:DesktopCommandContext, noteId:string = null) => {
|
||||||
if (!props.noteId) return;
|
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||||
|
|
||||||
if (props.noteIsBeingWatched) {
|
if (!noteId) return;
|
||||||
CommandService.instance().execute('stopExternalEditing', { noteId: props.noteId });
|
|
||||||
|
if (context.state.watchedNoteFiles.includes(noteId)) {
|
||||||
|
CommandService.instance().execute('stopExternalEditing', noteId);
|
||||||
} else {
|
} else {
|
||||||
CommandService.instance().execute('startExternalEditing', { noteId: props.noteId });
|
CommandService.instance().execute('startExternalEditing', noteId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:Props) => {
|
enabledCondition: 'oneNoteSelected',
|
||||||
return !!props.noteId;
|
mapStateToTitle: (state:any) => {
|
||||||
},
|
const noteId = stateUtils.selectedNoteId(state);
|
||||||
mapStateToProps: (state:AppState):Props => {
|
return state.watchedNoteFiles.includes(noteId) ? _('Stop') : '';
|
||||||
const noteId = state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null;
|
|
||||||
return {
|
|
||||||
noteId: noteId,
|
|
||||||
noteIsBeingWatched: noteId ? state.watchedNoteFiles.includes(noteId) : false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
title: (props:Props) => {
|
|
||||||
return props.noteIsBeingWatched ? _('Stop') : '';
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,8 @@ const getLabel = (commandName: string) => {
|
|||||||
return _('Hide Joplin');
|
return _('Hide Joplin');
|
||||||
case 'closeWindow':
|
case 'closeWindow':
|
||||||
return _('Close Window');
|
return _('Close Window');
|
||||||
|
case 'commandPalette':
|
||||||
|
return _('Command palette');
|
||||||
case 'config':
|
case 'config':
|
||||||
return shim.isMac() ? _('Preferences') : _('Options');
|
return shim.isMac() ? _('Preferences') : _('Options');
|
||||||
default:
|
default:
|
||||||
|
@ -61,7 +61,7 @@ const commands = [
|
|||||||
require('./commands/showShareNoteDialog'),
|
require('./commands/showShareNoteDialog'),
|
||||||
require('./commands/toggleEditors'),
|
require('./commands/toggleEditors'),
|
||||||
require('./commands/toggleNoteList'),
|
require('./commands/toggleNoteList'),
|
||||||
require('./commands/toggleSidebar'),
|
require('./commands/toggleSideBar'),
|
||||||
require('./commands/toggleVisiblePanes'),
|
require('./commands/toggleVisiblePanes'),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -320,7 +320,7 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||||||
window.removeEventListener('resize', this.window_resize);
|
window.removeEventListener('resize', this.window_resize);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSidebar() {
|
toggleSideBar() {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'SIDEBAR_VISIBILITY_TOGGLE',
|
type: 'SIDEBAR_VISIBILITY_TOGGLE',
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import eventManager from 'lib/eventManager';
|
import eventManager from 'lib/eventManager';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import { stateUtils } from 'lib/reducer';
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const BaseModel = require('lib/BaseModel');
|
|
||||||
const { time } = require('lib/time-utils');
|
const { time } = require('lib/time-utils');
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -13,7 +13,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteId }:any) => {
|
execute: async (context:CommandContext, noteId:string = null) => {
|
||||||
|
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||||
|
|
||||||
const note = await Note.load(noteId);
|
const note = await Note.load(noteId);
|
||||||
|
|
||||||
const defaultDate = new Date(Date.now() + 2 * 3600 * 1000);
|
const defaultDate = new Date(Date.now() + 2 * 3600 * 1000);
|
||||||
@ -51,25 +53,12 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
title: (props:any):string => {
|
|
||||||
if (!props.noteId) return null;
|
|
||||||
if (!props.noteTodoDue) return null;
|
|
||||||
return time.formatMsToLocal(props.noteTodoDue);
|
|
||||||
},
|
|
||||||
isEnabled: (props:any):boolean => {
|
|
||||||
if (!props.noteId) return false;
|
|
||||||
return !!props.noteIsTodo && !props.noteTodoCompleted;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
const noteId = state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null;
|
|
||||||
const note = noteId ? BaseModel.byId(state.notes, noteId) : null;
|
|
||||||
|
|
||||||
return {
|
enabledCondition: 'oneNoteSelected && noteIsTodo && !noteTodoCompleted',
|
||||||
noteId: note ? noteId : null,
|
|
||||||
noteIsTodo: note ? note.is_todo : false,
|
mapStateToTitle: (state:any) => {
|
||||||
noteTodoCompleted: note ? note.todo_completed : false,
|
const note = stateUtils.selectedNote(state);
|
||||||
noteTodoDue: note ? note.todo_due : null,
|
return note && note.todo_due ? time.formatMsToLocal(note.todo_due) : null;
|
||||||
};
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import shim from 'lib/shim';
|
import shim from 'lib/shim';
|
||||||
import InteropServiceHelper from '../../../InteropServiceHelper';
|
import InteropServiceHelper from '../../../InteropServiceHelper';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
@ -12,8 +12,10 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteIds }:any) => {
|
execute: async (context:CommandContext, noteIds:string[] = null) => {
|
||||||
try {
|
try {
|
||||||
|
noteIds = noteIds || context.state.selectedNoteIds;
|
||||||
|
|
||||||
if (!noteIds.length) throw new Error('No notes selected for pdf export');
|
if (!noteIds.length) throw new Error('No notes selected for pdf export');
|
||||||
|
|
||||||
let path = null;
|
let path = null;
|
||||||
@ -22,7 +24,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
filters: [{ name: _('PDF File'), extensions: ['pdf'] }],
|
filters: [{ name: _('PDF File'), extensions: ['pdf'] }],
|
||||||
defaultPath: await InteropServiceHelper.defaultFilename(noteIds[0], 'pdf'),
|
defaultPath: await InteropServiceHelper.defaultFilename(noteIds[0], 'pdf'),
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
path = bridge().showOpenDialog({
|
path = bridge().showOpenDialog({
|
||||||
properties: ['openDirectory', 'createDirectory'],
|
properties: ['openDirectory', 'createDirectory'],
|
||||||
@ -50,13 +51,7 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
bridge().showErrorMessageBox(error.message);
|
bridge().showErrorMessageBox(error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
|
||||||
return !!props.noteIds.length;
|
enabledCondition: 'someNotesSelected',
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
noteIds: state.selectedNoteIds,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import { CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'hideModalMessage',
|
name: 'hideModalMessage',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
@ -10,7 +10,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteIds }:any) => {
|
execute: async (context:CommandContext, noteIds:string[] = null) => {
|
||||||
|
noteIds = noteIds || context.state.selectedNoteIds;
|
||||||
|
|
||||||
const folders:any[] = await Folder.sortFolderTree();
|
const folders:any[] = await Folder.sortFolderTree();
|
||||||
const startFolders:any[] = [];
|
const startFolders:any[] = [];
|
||||||
const maxDepth = 15;
|
const maxDepth = 15;
|
||||||
@ -42,13 +44,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
enabledCondition: 'someNotesSelected',
|
||||||
return !!props.noteIds.length;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
noteIds: state.selectedNoteIds,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import { CommandContext, CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
const bridge = require('electron').remote.require('./bridge').default;
|
||||||
@ -11,7 +11,7 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ parentId }:any) => {
|
execute: async (_context:CommandContext, parentId:string = null) => {
|
||||||
comp.setState({
|
comp.setState({
|
||||||
promptOptions: {
|
promptOptions: {
|
||||||
label: _('Notebook title:'),
|
label: _('Notebook title:'),
|
||||||
@ -39,8 +39,5 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
title: () => {
|
|
||||||
return _('New notebook');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { utils, CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { utils, CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const Folder = require('lib/models/Folder');
|
|
||||||
const TemplateUtils = require('lib/TemplateUtils');
|
const TemplateUtils = require('lib/TemplateUtils');
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -13,7 +12,7 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ template, isTodo }:any) => {
|
execute: async (_context:CommandContext, template:string = null, isTodo:boolean = false) => {
|
||||||
const folderId = Setting.value('activeFolderId');
|
const folderId = Setting.value('activeFolderId');
|
||||||
if (!folderId) return;
|
if (!folderId) return;
|
||||||
|
|
||||||
@ -34,12 +33,6 @@ export const runtime = ():CommandRuntime => {
|
|||||||
id: newNote.id,
|
id: newNote.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEnabled: () => {
|
enabledCondition: 'oneFolderSelected && !inConflictFolder',
|
||||||
const { folders, selectedFolderId } = utils.store.getState();
|
|
||||||
return !!folders.length && selectedFolderId !== Folder.conflictFolderId();
|
|
||||||
},
|
|
||||||
title: () => {
|
|
||||||
return _('New note');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import CommandService, { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import CommandService, { CommandContext, CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -9,14 +9,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ template }:any) => {
|
execute: async (_context:CommandContext, template:string = null) => {
|
||||||
return CommandService.instance().execute('newNote', { template: template, isTodo: true });
|
return CommandService.instance().execute('newNote', template, true);
|
||||||
},
|
|
||||||
isEnabled: () => {
|
|
||||||
return CommandService.instance().isEnabled('newNote', {});
|
|
||||||
},
|
|
||||||
title: () => {
|
|
||||||
return _('New to-do');
|
|
||||||
},
|
},
|
||||||
|
enabledCondition: 'oneFolderSelected && !inConflictFolder',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
const bridge = require('electron').remote.require('./bridge').default;
|
||||||
|
|
||||||
@ -10,8 +10,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteIds }:any) => {
|
execute: async (context:CommandContext, noteIds:string[] = null) => {
|
||||||
// TODO: test
|
noteIds = noteIds || context.state.selectedNoteIds;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (noteIds.length !== 1) throw new Error(_('Only one note can be printed at a time.'));
|
if (noteIds.length !== 1) throw new Error(_('Only one note can be printed at a time.'));
|
||||||
await comp.printTo_('printer', { noteId: noteIds[0] });
|
await comp.printTo_('printer', { noteId: noteIds[0] });
|
||||||
@ -19,13 +20,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
bridge().showErrorMessageBox(error.message);
|
bridge().showErrorMessageBox(error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
enabledCondition: 'someNotesSelected',
|
||||||
return !!props.noteIds.length;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
noteIds: state.selectedNoteIds,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
const bridge = require('electron').remote.require('./bridge').default;
|
||||||
@ -10,7 +10,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ folderId }:any) => {
|
execute: async (context:CommandContext, folderId:string = null) => {
|
||||||
|
folderId = folderId || context.state.selectedFolderId;
|
||||||
|
|
||||||
const folder = await Folder.load(folderId);
|
const folder = await Folder.load(folderId);
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const Tag = require('lib/models/Tag');
|
const Tag = require('lib/models/Tag');
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
const bridge = require('electron').remote.require('./bridge').default;
|
||||||
@ -10,7 +10,10 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ tagId }:any) => {
|
execute: async (context:CommandContext, tagId:string = null) => {
|
||||||
|
tagId = tagId || context.state.selectedTagId;
|
||||||
|
if (!tagId) return;
|
||||||
|
|
||||||
const tag = await Tag.load(tagId);
|
const tag = await Tag.load(tagId);
|
||||||
if (tag) {
|
if (tag) {
|
||||||
comp.setState({
|
comp.setState({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel');
|
||||||
const uuid = require('lib/uuid').default;
|
const uuid = require('lib/uuid').default;
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ query }:any) => {
|
execute: async (_context:CommandContext, query:string) => {
|
||||||
if (!comp.searchId_) comp.searchId_ = uuid.create();
|
if (!comp.searchId_) comp.searchId_ = uuid.create();
|
||||||
|
|
||||||
comp.props.dispatch({
|
comp.props.dispatch({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import CommandService, { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const TemplateUtils = require('lib/TemplateUtils');
|
const TemplateUtils = require('lib/TemplateUtils');
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteType }:any) => {
|
execute: async (_context:CommandContext, noteType:string) => {
|
||||||
comp.setState({
|
comp.setState({
|
||||||
promptOptions: {
|
promptOptions: {
|
||||||
label: _('Template file:'),
|
label: _('Template file:'),
|
||||||
@ -18,9 +18,9 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
onClose: async (answer:any) => {
|
onClose: async (answer:any) => {
|
||||||
if (answer) {
|
if (answer) {
|
||||||
if (noteType === 'note' || noteType === 'todo') {
|
if (noteType === 'note' || noteType === 'todo') {
|
||||||
CommandService.instance().execute('newNote', { template: answer.value, isTodo: noteType === 'todo' });
|
CommandService.instance().execute('newNote', answer.value, noteType === 'todo');
|
||||||
} else {
|
} else {
|
||||||
CommandService.instance().execute('insertText', { value: TemplateUtils.render(answer.value) });
|
CommandService.instance().execute('insertText', TemplateUtils.render(answer.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const Tag = require('lib/models/Tag');
|
const Tag = require('lib/models/Tag');
|
||||||
|
|
||||||
@ -10,7 +10,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteIds }:any) => {
|
execute: async (context:CommandContext, noteIds:string[] = null) => {
|
||||||
|
noteIds = noteIds || context.state.selectedNoteIds;
|
||||||
|
|
||||||
const tags = await Tag.commonTagsByNoteIds(noteIds);
|
const tags = await Tag.commonTagsByNoteIds(noteIds);
|
||||||
const startTags = tags
|
const startTags = tags
|
||||||
.map((a:any) => {
|
.map((a:any) => {
|
||||||
@ -64,11 +66,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEnabled: (props:any) => {
|
enabledCondition: 'someNotesSelected',
|
||||||
return !!props.noteIds.length;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return { noteIds: state.selectedNoteIds };
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import { CommandDeclaration, CommandRuntime, CommandContext } from 'lib/services/CommandService';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'showModalMessage',
|
name: 'showModalMessage',
|
||||||
@ -7,7 +7,7 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ message }:any) => {
|
execute: async (_context:CommandContext, message:string) => {
|
||||||
comp.setState({
|
comp.setState({
|
||||||
modalLayer: {
|
modalLayer: {
|
||||||
visible: true,
|
visible: true,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import { stateUtils } from 'lib/reducer';
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -9,7 +10,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteId }:any) => {
|
execute: async (context:CommandContext, noteId:string = null) => {
|
||||||
|
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||||
|
|
||||||
const note = await Note.load(noteId);
|
const note = await Note.load(noteId);
|
||||||
if (note) {
|
if (note) {
|
||||||
comp.setState({
|
comp.setState({
|
||||||
@ -21,11 +24,7 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any) => {
|
|
||||||
return !!props.noteId;
|
enabledCondition: 'oneNoteSelected',
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return { noteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null };
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import CommandService, { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import { stateUtils } from 'lib/reducer';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'showNoteProperties',
|
name: 'showNoteProperties',
|
||||||
@ -9,7 +10,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteId }:any) => {
|
execute: async (context:CommandContext, noteId:string = null) => {
|
||||||
|
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||||
|
|
||||||
comp.setState({
|
comp.setState({
|
||||||
notePropertiesDialogOptions: {
|
notePropertiesDialogOptions: {
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
@ -20,11 +23,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEnabled: (props:any) => {
|
enabledCondition: 'oneNoteSelected',
|
||||||
return !!props.noteId;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return { noteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null };
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -8,7 +8,9 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteIds }:any) => {
|
execute: async (context:CommandContext, noteIds:string[] = null) => {
|
||||||
|
noteIds = noteIds || context.state.selectedNoteIds;
|
||||||
|
|
||||||
comp.setState({
|
comp.setState({
|
||||||
shareNoteDialogOptions: {
|
shareNoteDialogOptions: {
|
||||||
noteIds: noteIds,
|
noteIds: noteIds,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import { CommandDeclaration, CommandRuntime, CommandContext } from 'lib/services/CommandService';
|
||||||
import Setting from 'lib/models/Setting';
|
import Setting from 'lib/models/Setting';
|
||||||
import { stateUtils } from 'lib/reducer';
|
import { stateUtils } from 'lib/reducer';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
@ -11,22 +11,14 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async (props:any) => {
|
execute: async (context:CommandContext) => {
|
||||||
// A bit of a hack, but for now don't allow changing code view
|
// A bit of a hack, but for now don't allow changing code view
|
||||||
// while a note is being saved as it will cause a problem with
|
// while a note is being saved as it will cause a problem with
|
||||||
// TinyMCE because it won't have time to send its content before
|
// TinyMCE because it won't have time to send its content before
|
||||||
// being switch to Ace Editor.
|
// being switch to Ace Editor.
|
||||||
if (props.hasNotesBeingSaved) return;
|
if (stateUtils.hasNotesBeingSaved(context.state)) return;
|
||||||
Setting.toggle('editor.codeView');
|
Setting.toggle('editor.codeView');
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
enabledCondition: '!notesAreBeingSaved && oneNoteSelected',
|
||||||
return !props.hasNotesBeingSaved && props.hasOneSelectedNote;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
|
|
||||||
hasOneSelectedNote: state.selectedNoteIds.length === 1,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import { CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import { CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'toggleSidebar',
|
name: 'toggleSideBar',
|
||||||
label: () => _('Toggle sidebar'),
|
label: () => _('Toggle sidebar'),
|
||||||
iconName: 'fas fa-bars',
|
iconName: 'fas fa-bars',
|
||||||
};
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import { CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -14,14 +14,7 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
type: 'NOTE_VISIBLE_PANES_TOGGLE',
|
type: 'NOTE_VISIBLE_PANES_TOGGLE',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
|
||||||
return props.settingEditorCodeView && props.selectedNoteIds.length === 1;
|
enabledCondition: 'markdownEditorVisible && oneNoteSelected',
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
selectedNoteIds: state.selectedNoteIds,
|
|
||||||
settingEditorCodeView: state.settings['editor.codeView'],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ import { Module } from 'lib/services/interop/types';
|
|||||||
import InteropServiceHelper from '../InteropServiceHelper';
|
import InteropServiceHelper from '../InteropServiceHelper';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
import { MenuItem, MenuItemLocation } from 'lib/services/plugins/api/types';
|
import { MenuItem, MenuItemLocation } from 'lib/services/plugins/api/types';
|
||||||
|
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
@ -108,7 +109,7 @@ const commandNames:string[] = [
|
|||||||
'attachFile',
|
'attachFile',
|
||||||
'focusSearch',
|
'focusSearch',
|
||||||
'showLocalSearch',
|
'showLocalSearch',
|
||||||
'toggleSidebar',
|
'toggleSideBar',
|
||||||
'toggleNoteList',
|
'toggleNoteList',
|
||||||
'toggleVisiblePanes',
|
'toggleVisiblePanes',
|
||||||
'toggleExternalEditing',
|
'toggleExternalEditing',
|
||||||
@ -157,7 +158,7 @@ function useMenu(props:Props) {
|
|||||||
|
|
||||||
if (Array.isArray(path)) path = path[0];
|
if (Array.isArray(path)) path = path[0];
|
||||||
|
|
||||||
CommandService.instance().execute('showModalMessage', { message: _('Importing from "%s" as "%s" format. Please wait...', path, module.format) });
|
CommandService.instance().execute('showModalMessage', _('Importing from "%s" as "%s" format. Please wait...', path, module.format));
|
||||||
|
|
||||||
const importOptions = {
|
const importOptions = {
|
||||||
path,
|
path,
|
||||||
@ -299,12 +300,12 @@ function useMenu(props:Props) {
|
|||||||
templateItems.push({
|
templateItems.push({
|
||||||
label: _('Create note from template'),
|
label: _('Create note from template'),
|
||||||
click: () => {
|
click: () => {
|
||||||
CommandService.instance().execute('selectTemplate', { noteType: 'note' });
|
CommandService.instance().execute('selectTemplate', 'note');
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: _('Create to-do from template'),
|
label: _('Create to-do from template'),
|
||||||
click: () => {
|
click: () => {
|
||||||
CommandService.instance().execute('selectTemplate', { noteType: 'todo' });
|
CommandService.instance().execute('selectTemplate', 'todo');
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: _('Insert template'),
|
label: _('Insert template'),
|
||||||
@ -532,7 +533,7 @@ function useMenu(props:Props) {
|
|||||||
view: {
|
view: {
|
||||||
label: _('&View'),
|
label: _('&View'),
|
||||||
submenu: [
|
submenu: [
|
||||||
menuItemDic.toggleSidebar,
|
menuItemDic.toggleSideBar,
|
||||||
menuItemDic.toggleNoteList,
|
menuItemDic.toggleNoteList,
|
||||||
menuItemDic.toggleVisiblePanes,
|
menuItemDic.toggleVisiblePanes,
|
||||||
{
|
{
|
||||||
@ -550,7 +551,6 @@ function useMenu(props:Props) {
|
|||||||
id: 'showNoteCounts',
|
id: 'showNoteCounts',
|
||||||
label: Setting.settingMetadata('showNoteCounts').label(),
|
label: Setting.settingMetadata('showNoteCounts').label(),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
// checked: Setting.value('showNoteCounts'),
|
|
||||||
click: () => {
|
click: () => {
|
||||||
Setting.setValue('showNoteCounts', !Setting.value('showNoteCounts'));
|
Setting.setValue('showNoteCounts', !Setting.value('showNoteCounts'));
|
||||||
},
|
},
|
||||||
@ -558,7 +558,6 @@ function useMenu(props:Props) {
|
|||||||
id: 'uncompletedTodosOnTop',
|
id: 'uncompletedTodosOnTop',
|
||||||
label: Setting.settingMetadata('uncompletedTodosOnTop').label(),
|
label: Setting.settingMetadata('uncompletedTodosOnTop').label(),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
// checked: Setting.value('uncompletedTodosOnTop'),
|
|
||||||
click: () => {
|
click: () => {
|
||||||
Setting.setValue('uncompletedTodosOnTop', !Setting.value('uncompletedTodosOnTop'));
|
Setting.setValue('uncompletedTodosOnTop', !Setting.value('uncompletedTodosOnTop'));
|
||||||
},
|
},
|
||||||
@ -566,7 +565,6 @@ function useMenu(props:Props) {
|
|||||||
id: 'showCompletedTodos',
|
id: 'showCompletedTodos',
|
||||||
label: Setting.settingMetadata('showCompletedTodos').label(),
|
label: Setting.settingMetadata('showCompletedTodos').label(),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
// checked: Setting.value('showCompletedTodos'),
|
|
||||||
click: () => {
|
click: () => {
|
||||||
Setting.setValue('showCompletedTodos', !Setting.value('showCompletedTodos'));
|
Setting.setValue('showCompletedTodos', !Setting.value('showCompletedTodos'));
|
||||||
},
|
},
|
||||||
@ -786,9 +784,13 @@ function useMenu(props:Props) {
|
|||||||
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime]);
|
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const whenClauseContext = CommandService.instance().currentWhenClauseContext();
|
||||||
|
|
||||||
for (const commandName in props.menuItemProps) {
|
for (const commandName in props.menuItemProps) {
|
||||||
if (!props.menuItemProps[commandName]) continue;
|
const p = props.menuItemProps[commandName];
|
||||||
menuItemSetEnabled(commandName, CommandService.instance().isEnabled(commandName, props.menuItemProps[commandName]));
|
if (!p) continue;
|
||||||
|
const enabled = 'enabled' in p ? p.enabled : CommandService.instance().isEnabled(commandName, whenClauseContext);
|
||||||
|
menuItemSetEnabled(commandName, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutButtonSequenceOptions = Setting.enumOptions('layoutButtonSequence');
|
const layoutButtonSequenceOptions = Setting.enumOptions('layoutButtonSequence');
|
||||||
@ -858,8 +860,10 @@ function MenuBar(props:Props):JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state:AppState) => {
|
const mapStateToProps = (state:AppState) => {
|
||||||
|
const whenClauseContext = stateToWhenClauseContext(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
menuItemProps: menuUtils.commandsToMenuItemProps(state, commandNames.concat(pluginCommandNames(state.pluginService.plugins))),
|
menuItemProps: menuUtils.commandsToMenuItemProps(commandNames.concat(pluginCommandNames(state.pluginService.plugins)), whenClauseContext),
|
||||||
routeName: state.route.routeName,
|
routeName: state.route.routeName,
|
||||||
selectedFolderId: state.selectedFolderId,
|
selectedFolderId: state.selectedFolderId,
|
||||||
layoutButtonSequence: state.settings.layoutButtonSequence,
|
layoutButtonSequence: state.settings.layoutButtonSequence,
|
||||||
|
@ -46,8 +46,6 @@ function formatReadTime(readTimeMinutes: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function NoteContentPropertiesDialog(props:NoteContentPropertiesDialogProps) {
|
export default function NoteContentPropertiesDialog(props:NoteContentPropertiesDialogProps) {
|
||||||
|
|
||||||
console.info('MMMMMMMMMMMM', props.markupLanguage);
|
|
||||||
const theme = themeStyle(props.themeId);
|
const theme = themeStyle(props.themeId);
|
||||||
const tableBodyComps: JSX.Element[] = [];
|
const tableBodyComps: JSX.Element[] = [];
|
||||||
// For the source Markdown
|
// For the source Markdown
|
||||||
|
@ -5,6 +5,7 @@ import { utils as pluginUtils } from 'lib/services/plugins/reducer';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { AppState } from '../../../../app';
|
import { AppState } from '../../../../app';
|
||||||
import ToolbarButtonUtils, { ToolbarButtonInfo } from 'lib/services/commands/ToolbarButtonUtils';
|
import ToolbarButtonUtils, { ToolbarButtonInfo } from 'lib/services/commands/ToolbarButtonUtils';
|
||||||
|
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
|
||||||
const { buildStyle } = require('lib/theme');
|
const { buildStyle } = require('lib/theme');
|
||||||
|
|
||||||
interface ToolbarProps {
|
interface ToolbarProps {
|
||||||
@ -31,6 +32,8 @@ function Toolbar(props:ToolbarProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
const whenClauseContext = stateToWhenClauseContext(state);
|
||||||
|
|
||||||
const commandNames = [
|
const commandNames = [
|
||||||
'historyBackward',
|
'historyBackward',
|
||||||
'historyForward',
|
'historyForward',
|
||||||
@ -53,7 +56,7 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
].concat(pluginUtils.commandNamesFromViews(state.pluginService.plugins, 'editorToolbar'));
|
].concat(pluginUtils.commandNamesFromViews(state.pluginService.plugins, 'editorToolbar'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons(state, commandNames),
|
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons(commandNames, whenClauseContext),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import eventManager from 'lib/eventManager';
|
|||||||
import { AppState } from '../../app';
|
import { AppState } from '../../app';
|
||||||
import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
|
import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
|
||||||
|
|
||||||
const { themeStyle } = require('lib/theme');
|
const { themeStyle } = require('lib/theme');
|
||||||
const { substrWithEllipsis } = require('lib/string-utils');
|
const { substrWithEllipsis } = require('lib/string-utils');
|
||||||
@ -247,9 +248,9 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
CommandService.instance().execute('focusElement', { target: 'noteList' });
|
CommandService.instance().execute('focusElement', 'noteList');
|
||||||
} else {
|
} else {
|
||||||
CommandService.instance().execute('focusElement', { target: 'noteBody' });
|
CommandService.instance().execute('focusElement', 'noteBody');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [props.dispatch]);
|
}, [props.dispatch]);
|
||||||
@ -364,7 +365,7 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
function renderTagBar() {
|
function renderTagBar() {
|
||||||
const theme = themeStyle(props.themeId);
|
const theme = themeStyle(props.themeId);
|
||||||
const noteIds = [formNote.id];
|
const noteIds = [formNote.id];
|
||||||
const instructions = <span onClick={() => { CommandService.instance().execute('setTags', { noteIds }); }} style={{ ...theme.clickableTextStyle, whiteSpace: 'nowrap' }}>Click to add tags...</span>;
|
const instructions = <span onClick={() => { CommandService.instance().execute('setTags', noteIds); }} style={{ ...theme.clickableTextStyle, whiteSpace: 'nowrap' }}>Click to add tags...</span>;
|
||||||
const tagList = props.selectedNoteTags.length ? <TagList items={props.selectedNoteTags} /> : null;
|
const tagList = props.selectedNoteTags.length ? <TagList items={props.selectedNoteTags} /> : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -565,6 +566,7 @@ export {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
const noteId = state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null;
|
const noteId = state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null;
|
||||||
|
const whenClauseContext = stateToWhenClauseContext(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
@ -587,15 +589,15 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
watchedResources: state.watchedResources,
|
watchedResources: state.watchedResources,
|
||||||
highlightedWords: state.highlightedWords,
|
highlightedWords: state.highlightedWords,
|
||||||
plugins: state.pluginService.plugins,
|
plugins: state.pluginService.plugins,
|
||||||
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons(state, [
|
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons([
|
||||||
'historyBackward',
|
'historyBackward',
|
||||||
'historyForward',
|
'historyForward',
|
||||||
'toggleEditors',
|
'toggleEditors',
|
||||||
'toggleExternalEditing',
|
'toggleExternalEditing',
|
||||||
]),
|
], whenClauseContext),
|
||||||
setTagsToolbarButtonInfo: toolbarButtonUtils.commandsToToolbarButtons(state, [
|
setTagsToolbarButtonInfo: toolbarButtonUtils.commandsToToolbarButtons([
|
||||||
'setTags',
|
'setTags',
|
||||||
])[0],
|
], whenClauseContext)[0],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -12,13 +12,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
execute: async () => {
|
execute: async () => {
|
||||||
comp.editorRef.current.execCommand({ name: 'focus' });
|
comp.editorRef.current.execCommand({ name: 'focus' });
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
enabledCondition: 'oneNoteSelected',
|
||||||
return props.hasOneNoteSelected;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
hasOneNoteSelected: state.selectedNoteIds.length === 1,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -13,14 +13,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
if (!comp.titleInputRef.current) return;
|
if (!comp.titleInputRef.current) return;
|
||||||
comp.titleInputRef.current.focus();
|
comp.titleInputRef.current.focus();
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
enabledCondition: 'oneNoteSelected',
|
||||||
return props.hasOneNoteSelected;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
hasOneNoteSelected: state.selectedNoteIds.length === 1,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
@ -16,11 +16,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
if (comp.noteSearchBarRef.current) comp.noteSearchBarRef.current.wrappedInstance.focus();
|
if (comp.noteSearchBarRef.current) comp.noteSearchBarRef.current.wrappedInstance.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any) => {
|
enabledCondition: 'oneNoteSelected',
|
||||||
return !!props.noteId;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return { noteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null };
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'showRevisions',
|
name: 'showRevisions',
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FormNote, ScrollOptionTypes } from './types';
|
import { FormNote, ScrollOptionTypes } from './types';
|
||||||
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
|
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
|
||||||
import CommandService, { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from 'lib/services/CommandService';
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const BaseModel = require('lib/BaseModel');
|
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const { MarkupToHtml } = require('lib/joplin-renderer');
|
|
||||||
|
|
||||||
const commandsWithDependencies = [
|
const commandsWithDependencies = [
|
||||||
require('../commands/showLocalSearch'),
|
require('../commands/showLocalSearch'),
|
||||||
@ -25,49 +23,30 @@ interface HookDependencies {
|
|||||||
|
|
||||||
function editorCommandRuntime(declaration:CommandDeclaration, editorRef:any):CommandRuntime {
|
function editorCommandRuntime(declaration:CommandDeclaration, editorRef:any):CommandRuntime {
|
||||||
return {
|
return {
|
||||||
execute: async (props:any) => {
|
execute: async (_context:CommandContext, ...args:any[]) => {
|
||||||
// console.info('Running editor command:', declaration.name, props);
|
|
||||||
if (!editorRef.current.execCommand) {
|
if (!editorRef.current.execCommand) {
|
||||||
reg.logger().warn('Received command, but editor cannot execute commands', declaration.name);
|
reg.logger().warn('Received command, but editor cannot execute commands', declaration.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declaration.name === 'insertDateTime') {
|
||||||
|
return editorRef.current.execCommand({
|
||||||
|
name: 'insertText',
|
||||||
|
value: time.formatMsToLocal(new Date().getTime()),
|
||||||
|
});
|
||||||
|
} else if (declaration.name === 'scrollToHash') {
|
||||||
|
return editorRef.current.scrollTo({
|
||||||
|
type: ScrollOptionTypes.Hash,
|
||||||
|
value: args[0],
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (declaration.name === 'insertDateTime') {
|
return editorRef.current.execCommand({
|
||||||
return editorRef.current.execCommand({
|
name: declaration.name,
|
||||||
name: 'insertText',
|
value: args[0],
|
||||||
value: time.formatMsToLocal(new Date().getTime()),
|
});
|
||||||
});
|
|
||||||
} else if (declaration.name === 'scrollToHash') {
|
|
||||||
return editorRef.current.scrollTo({
|
|
||||||
type: ScrollOptionTypes.Hash,
|
|
||||||
value: props.hash,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return editorRef.current.execCommand({
|
|
||||||
name: declaration.name,
|
|
||||||
value: props.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any) => {
|
enabledCondition: '!modalDialogVisible && markdownEditorPaneVisible && oneNoteSelected && noteIsMarkdown',
|
||||||
if (props.isDialogVisible) return false;
|
|
||||||
if (props.markdownEditorViewerOnly) return false;
|
|
||||||
if (!props.hasSelectedNote) return false;
|
|
||||||
return props.isMarkdownNote;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
const noteId = state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null;
|
|
||||||
const note = noteId ? BaseModel.byId(state.notes, noteId) : null;
|
|
||||||
const isMarkdownNote = note ? note.markup_language === MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN : false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
// True when the Markdown editor is active, and only the viewer pane is visible
|
|
||||||
// In this case, all editor-related shortcuts are disabled.
|
|
||||||
markdownEditorViewerOnly: state.settings['editor.codeView'] && state.noteVisiblePanes.length === 1 && state.noteVisiblePanes[0] === 'viewer',
|
|
||||||
hasSelectedNote: !!note,
|
|
||||||
isDialogVisible: !!Object.keys(state.visibleDialogs).length,
|
|
||||||
isMarkdownNote: isMarkdownNote,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ const StyledRoot = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: ${(props:any) => props.theme.backgroundColor3};
|
background-color: ${(props:any) => props.theme.backgroundColor3};
|
||||||
border-right: 1px solid ${(props:any) => props.theme.dividerColor},
|
border-right: 1px solid ${(props:any) => props.theme.dividerColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class NoteListComponent extends React.Component {
|
class NoteListComponent extends React.Component {
|
||||||
@ -386,9 +386,9 @@ class NoteListComponent extends React.Component {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
CommandService.instance().execute('focusElement', { target: 'sideBar' });
|
CommandService.instance().execute('focusElement', 'sideBar');
|
||||||
} else {
|
} else {
|
||||||
CommandService.instance().execute('focusElement', { target: 'noteTitle' });
|
CommandService.instance().execute('focusElement', 'noteTitle');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import { stateUtils } from 'lib/reducer';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'focusElementNoteList',
|
name: 'focusElementNoteList',
|
||||||
@ -9,19 +10,14 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ noteId }:any) => {
|
execute: async (context:CommandContext, noteId:string = null) => {
|
||||||
|
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||||
|
|
||||||
if (noteId) {
|
if (noteId) {
|
||||||
const ref = comp.itemAnchorRef(noteId);
|
const ref = comp.itemAnchorRef(noteId);
|
||||||
if (ref) ref.focus();
|
if (ref) ref.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
enabledCondition: 'noteListHasNotes',
|
||||||
return !!props.noteId;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -52,13 +52,13 @@ export default function NoteListControls(props:Props) {
|
|||||||
return (
|
return (
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<StyledButton
|
<StyledButton
|
||||||
tooltip={CommandService.instance().title('newTodo', {})}
|
tooltip={CommandService.instance().label('newTodo')}
|
||||||
iconName="far fa-check-square"
|
iconName="far fa-check-square"
|
||||||
level={ButtonLevel.Primary}
|
level={ButtonLevel.Primary}
|
||||||
onClick={onNewTodoButtonClick}
|
onClick={onNewTodoButtonClick}
|
||||||
/>
|
/>
|
||||||
<StyledButton
|
<StyledButton
|
||||||
tooltip={CommandService.instance().title('newNote', {})}
|
tooltip={CommandService.instance().label('newNote')}
|
||||||
iconName="icon-note"
|
iconName="icon-note"
|
||||||
level={ButtonLevel.Primary}
|
level={ButtonLevel.Primary}
|
||||||
onClick={onNewNoteButtonClick}
|
onClick={onNewNoteButtonClick}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
|
@ -3,6 +3,7 @@ import CommandService from 'lib/services/CommandService';
|
|||||||
import ToolbarBase from '../ToolbarBase';
|
import ToolbarBase from '../ToolbarBase';
|
||||||
import { utils as pluginUtils } from 'lib/services/plugins/reducer';
|
import { utils as pluginUtils } from 'lib/services/plugins/reducer';
|
||||||
import ToolbarButtonUtils, { ToolbarButtonInfo } from 'lib/services/commands/ToolbarButtonUtils';
|
import ToolbarButtonUtils, { ToolbarButtonInfo } from 'lib/services/commands/ToolbarButtonUtils';
|
||||||
|
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { buildStyle } = require('lib/theme');
|
const { buildStyle } = require('lib/theme');
|
||||||
|
|
||||||
@ -32,12 +33,14 @@ function NoteToolbar(props:NoteToolbarProps) {
|
|||||||
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
|
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
|
||||||
|
|
||||||
const mapStateToProps = (state:any) => {
|
const mapStateToProps = (state:any) => {
|
||||||
|
const whenClauseContext = stateToWhenClauseContext(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons(state, [
|
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons([
|
||||||
'editAlarm',
|
'editAlarm',
|
||||||
'toggleVisiblePanes',
|
'toggleVisiblePanes',
|
||||||
'showNoteProperties',
|
'showNoteProperties',
|
||||||
].concat(pluginUtils.commandNamesFromViews(state.pluginService.plugins, 'noteToolbar'))),
|
].concat(pluginUtils.commandNamesFromViews(state.pluginService.plugins, 'noteToolbar')), whenClauseContext),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ const debounce = require('debounce');
|
|||||||
export default function useSearch(query:string) {
|
export default function useSearch(query:string) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const search = debounce((query:string) => {
|
const search = debounce((query:string) => {
|
||||||
CommandService.instance().execute('search', { query });
|
CommandService.instance().execute('search', query);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
search(query);
|
search(query);
|
||||||
|
@ -230,7 +230,7 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
|
|
||||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||||
menu.append(
|
menu.append(
|
||||||
new MenuItem(menuUtils.commandToStatefulMenuItem('newFolder', { parentId: itemId }))
|
new MenuItem(menuUtils.commandToStatefulMenuItem('newFolder', itemId))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +259,7 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||||
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('renameFolder', { folderId: itemId })));
|
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('renameFolder', itemId)));
|
||||||
|
|
||||||
menu.append(new MenuItem({ type: 'separator' }));
|
menu.append(new MenuItem({ type: 'separator' }));
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
|
|
||||||
if (itemType === BaseModel.TYPE_TAG) {
|
if (itemType === BaseModel.TYPE_TAG) {
|
||||||
menu.append(new MenuItem(
|
menu.append(new MenuItem(
|
||||||
menuUtils.commandToStatefulMenuItem('renameTag', { tagId: itemId })
|
menuUtils.commandToStatefulMenuItem('renameTag', itemId)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,9 +510,9 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
CommandService.instance().execute('focusElement', { target: 'noteBody' });
|
CommandService.instance().execute('focusElement', 'noteBody');
|
||||||
} else {
|
} else {
|
||||||
CommandService.instance().execute('focusElement', { target: 'noteList' });
|
CommandService.instance().execute('focusElement', 'noteList');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +559,7 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
iconAnimation={iconAnimation}
|
iconAnimation={iconAnimation}
|
||||||
title={label}
|
title={label}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
CommandService.instance().execute('synchronize', { syncStarted: type !== 'sync' });
|
CommandService.instance().execute('synchronize', type !== 'sync');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { CommandRuntime, CommandDeclaration } from '../../../lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from 'lib/services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
|
import { DesktopCommandContext } from 'ElectronClient/services/commands/types';
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'focusElementSideBar',
|
name: 'focusElementSideBar',
|
||||||
@ -9,8 +10,10 @@ export const declaration:CommandDeclaration = {
|
|||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = (comp:any):CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ sidebarVisibility }:any) => {
|
execute: async (context:DesktopCommandContext) => {
|
||||||
if (sidebarVisibility) {
|
const sideBarVisible = !!context.state.sidebarVisibility;
|
||||||
|
|
||||||
|
if (sideBarVisible) {
|
||||||
const item = comp.selectedItem();
|
const item = comp.selectedItem();
|
||||||
if (item) {
|
if (item) {
|
||||||
const anchorRef = comp.anchorItemRefs[item.type][item.id];
|
const anchorRef = comp.anchorItemRefs[item.type][item.id];
|
||||||
@ -21,13 +24,7 @@ export const runtime = (comp:any):CommandRuntime => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any):boolean => {
|
|
||||||
return props.sidebarVisibility;
|
enabledCondition: 'sideBarVisible',
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
sidebarVisibility: state.sidebarVisibility,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -40,11 +40,11 @@ export default class NoteListUtils {
|
|||||||
|
|
||||||
if (!hasEncrypted) {
|
if (!hasEncrypted) {
|
||||||
menu.append(
|
menu.append(
|
||||||
new MenuItem(menuUtils.commandToStatefulMenuItem('setTags', { noteIds }))
|
new MenuItem(menuUtils.commandToStatefulMenuItem('setTags', noteIds))
|
||||||
);
|
);
|
||||||
|
|
||||||
menu.append(
|
menu.append(
|
||||||
new MenuItem(menuUtils.commandToStatefulMenuItem('moveToFolder', { noteIds }))
|
new MenuItem(menuUtils.commandToStatefulMenuItem('moveToFolder', noteIds))
|
||||||
);
|
);
|
||||||
|
|
||||||
menu.append(
|
menu.append(
|
||||||
@ -63,7 +63,7 @@ export default class NoteListUtils {
|
|||||||
|
|
||||||
if (singleNoteId) {
|
if (singleNoteId) {
|
||||||
const cmd = props.watchedNoteFiles.includes(singleNoteId) ? 'stopExternalEditing' : 'startExternalEditing';
|
const cmd = props.watchedNoteFiles.includes(singleNoteId) ? 'stopExternalEditing' : 'startExternalEditing';
|
||||||
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem(cmd, { noteId: singleNoteId })));
|
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem(cmd, singleNoteId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteIds.length <= 1) {
|
if (noteIds.length <= 1) {
|
||||||
@ -132,7 +132,7 @@ export default class NoteListUtils {
|
|||||||
|
|
||||||
menu.append(
|
menu.append(
|
||||||
new MenuItem(
|
new MenuItem(
|
||||||
menuUtils.commandToStatefulMenuItem('showShareNoteDialog', { noteIds: noteIds.slice() })
|
menuUtils.commandToStatefulMenuItem('showShareNoteDialog', noteIds.slice())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ export default class NoteListUtils {
|
|||||||
|
|
||||||
exportMenu.append(
|
exportMenu.append(
|
||||||
new MenuItem(
|
new MenuItem(
|
||||||
menuUtils.commandToStatefulMenuItem('exportPdf', { noteIds: noteIds })
|
menuUtils.commandToStatefulMenuItem('exportPdf', noteIds)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
const React = require('react');
|
import * as React from 'react';
|
||||||
|
import { AppState } from '../app';
|
||||||
|
import CommandService, { SearchResult as CommandSearchResult } from 'lib/services/CommandService';
|
||||||
|
import KeymapService from 'lib/services/KeymapService';
|
||||||
|
import shim from 'lib/shim';
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const { themeStyle } = require('lib/theme');
|
const { themeStyle } = require('lib/theme');
|
||||||
const CommandService = require('lib/services/CommandService').default;
|
|
||||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel');
|
||||||
const Tag = require('lib/models/Tag');
|
const Tag = require('lib/models/Tag');
|
||||||
@ -12,32 +16,73 @@ const { ItemList } = require('../gui/ItemList.min');
|
|||||||
const HelpButton = require('../gui/HelpButton.min');
|
const HelpButton = require('../gui/HelpButton.min');
|
||||||
const { surroundKeywords, nextWhitespaceIndex, removeDiacritics } = require('lib/string-utils.js');
|
const { surroundKeywords, nextWhitespaceIndex, removeDiacritics } = require('lib/string-utils.js');
|
||||||
const { mergeOverlappingIntervals } = require('lib/ArrayUtils.js');
|
const { mergeOverlappingIntervals } = require('lib/ArrayUtils.js');
|
||||||
const PLUGIN_NAME = 'gotoAnything';
|
|
||||||
const markupLanguageUtils = require('lib/markupLanguageUtils');
|
const markupLanguageUtils = require('lib/markupLanguageUtils');
|
||||||
const KeymapService = require('lib/services/KeymapService.js').default;
|
|
||||||
const shim = require('lib/shim').default;
|
const PLUGIN_NAME = 'gotoAnything';
|
||||||
|
|
||||||
|
interface SearchResult {
|
||||||
|
id: string,
|
||||||
|
title: string,
|
||||||
|
parent_id: string,
|
||||||
|
fields: string[],
|
||||||
|
fragments?: string,
|
||||||
|
path?: string,
|
||||||
|
type?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
themeId: number,
|
||||||
|
dispatch: Function,
|
||||||
|
folders: any[],
|
||||||
|
showCompletedTodos: boolean,
|
||||||
|
userData: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
query: string,
|
||||||
|
results: SearchResult[],
|
||||||
|
selectedItemId: string,
|
||||||
|
keywords: string[],
|
||||||
|
listType: number,
|
||||||
|
showHelp: boolean,
|
||||||
|
resultsInBody: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
class GotoAnything {
|
class GotoAnything {
|
||||||
|
|
||||||
onTrigger() {
|
public dispatch:Function;
|
||||||
|
public static Dialog:any;
|
||||||
|
public static manifest:any;
|
||||||
|
|
||||||
|
onTrigger(event:any) {
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'PLUGINLEGACY_DIALOG_SET',
|
type: 'PLUGINLEGACY_DIALOG_SET',
|
||||||
open: true,
|
open: true,
|
||||||
pluginName: PLUGIN_NAME,
|
pluginName: PLUGIN_NAME,
|
||||||
|
userData: event.userData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Dialog extends React.PureComponent {
|
class Dialog extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
constructor() {
|
private fuzzy_:boolean;
|
||||||
super();
|
private styles_:any;
|
||||||
|
private inputRef:any;
|
||||||
|
private itemListRef:any;
|
||||||
|
private listUpdateIID_:any;
|
||||||
|
private markupToHtml_:any;
|
||||||
|
|
||||||
|
constructor(props:Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
this.fuzzy_ = false;
|
this.fuzzy_ = false;
|
||||||
|
|
||||||
|
const startString = props?.userData?.startString ? props?.userData?.startString : '';
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
query: '',
|
query: startString,
|
||||||
results: [],
|
results: [],
|
||||||
selectedItemId: null,
|
selectedItemId: null,
|
||||||
keywords: [],
|
keywords: [],
|
||||||
@ -55,19 +100,25 @@ class Dialog extends React.PureComponent {
|
|||||||
this.input_onChange = this.input_onChange.bind(this);
|
this.input_onChange = this.input_onChange.bind(this);
|
||||||
this.input_onKeyDown = this.input_onKeyDown.bind(this);
|
this.input_onKeyDown = this.input_onKeyDown.bind(this);
|
||||||
this.modalLayer_onClick = this.modalLayer_onClick.bind(this);
|
this.modalLayer_onClick = this.modalLayer_onClick.bind(this);
|
||||||
this.listItemRenderer = this.listItemRenderer.bind(this);
|
this.renderItem = this.renderItem.bind(this);
|
||||||
this.listItem_onClick = this.listItem_onClick.bind(this);
|
this.listItem_onClick = this.listItem_onClick.bind(this);
|
||||||
this.helpButton_onClick = this.helpButton_onClick.bind(this);
|
this.helpButton_onClick = this.helpButton_onClick.bind(this);
|
||||||
|
|
||||||
|
if (startString) this.scheduleListUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
style() {
|
style() {
|
||||||
const styleKey = [this.props.themeId, this.state.resultsInBody ? '1' : '0'].join('-');
|
const styleKey = [this.props.themeId, this.state.listType, this.state.resultsInBody ? '1' : '0'].join('-');
|
||||||
|
|
||||||
if (this.styles_[styleKey]) return this.styles_[styleKey];
|
if (this.styles_[styleKey]) return this.styles_[styleKey];
|
||||||
|
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const itemHeight = this.state.resultsInBody ? 84 : 64;
|
let itemHeight = this.state.resultsInBody ? 84 : 64;
|
||||||
|
|
||||||
|
if (this.state.listType === BaseModel.TYPE_COMMAND) {
|
||||||
|
itemHeight = 40;
|
||||||
|
}
|
||||||
|
|
||||||
this.styles_[styleKey] = {
|
this.styles_[styleKey] = {
|
||||||
dialogBox: Object.assign({}, theme.dialogBox, { minWidth: '50%', maxWidth: '50%' }),
|
dialogBox: Object.assign({}, theme.dialogBox, { minWidth: '50%', maxWidth: '50%' }),
|
||||||
@ -138,7 +189,7 @@ class Dialog extends React.PureComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event) {
|
onKeyDown(event:any) {
|
||||||
if (event.keyCode === 27) { // ESCAPE
|
if (event.keyCode === 27) { // ESCAPE
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
pluginName: PLUGIN_NAME,
|
pluginName: PLUGIN_NAME,
|
||||||
@ -148,7 +199,7 @@ class Dialog extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modalLayer_onClick(event) {
|
modalLayer_onClick(event:any) {
|
||||||
if (event.currentTarget == event.target) {
|
if (event.currentTarget == event.target) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
pluginName: PLUGIN_NAME,
|
pluginName: PLUGIN_NAME,
|
||||||
@ -162,7 +213,7 @@ class Dialog extends React.PureComponent {
|
|||||||
this.setState({ showHelp: !this.state.showHelp });
|
this.setState({ showHelp: !this.state.showHelp });
|
||||||
}
|
}
|
||||||
|
|
||||||
input_onChange(event) {
|
input_onChange(event:any) {
|
||||||
this.setState({ query: event.target.value });
|
this.setState({ query: event.target.value });
|
||||||
|
|
||||||
this.scheduleListUpdate();
|
this.scheduleListUpdate();
|
||||||
@ -177,7 +228,7 @@ class Dialog extends React.PureComponent {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeSearchQuery(query) {
|
makeSearchQuery(query:string) {
|
||||||
const output = [];
|
const output = [];
|
||||||
const splitted = query.split(' ');
|
const splitted = query.split(' ');
|
||||||
|
|
||||||
@ -190,7 +241,7 @@ class Dialog extends React.PureComponent {
|
|||||||
return output.join(' ');
|
return output.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
async keywords(searchQuery) {
|
async keywords(searchQuery:string) {
|
||||||
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery, this.fuzzy_);
|
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery, this.fuzzy_);
|
||||||
return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
||||||
}
|
}
|
||||||
@ -207,11 +258,28 @@ class Dialog extends React.PureComponent {
|
|||||||
if (!this.state.query) {
|
if (!this.state.query) {
|
||||||
this.setState({ results: [], keywords: [] });
|
this.setState({ results: [], keywords: [] });
|
||||||
} else {
|
} else {
|
||||||
let results = [];
|
let results:SearchResult[] = [];
|
||||||
let listType = null;
|
let listType = null;
|
||||||
let searchQuery = '';
|
let searchQuery = '';
|
||||||
|
let keywords = null;
|
||||||
|
|
||||||
if (this.state.query.indexOf('#') === 0) { // TAGS
|
if (this.state.query.indexOf(':') === 0) { // COMMANDS
|
||||||
|
const query = this.state.query.substr(1);
|
||||||
|
listType = BaseModel.TYPE_COMMAND;
|
||||||
|
keywords = [query];
|
||||||
|
|
||||||
|
const commandResults = CommandService.instance().searchCommands(query, true);
|
||||||
|
|
||||||
|
results = commandResults.map((result:CommandSearchResult) => {
|
||||||
|
return {
|
||||||
|
id: result.commandName,
|
||||||
|
title: result.title,
|
||||||
|
parent_id: null,
|
||||||
|
fields: [],
|
||||||
|
type: BaseModel.TYPE_COMMAND,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else if (this.state.query.indexOf('#') === 0) { // TAGS
|
||||||
listType = BaseModel.TYPE_TAG;
|
listType = BaseModel.TYPE_TAG;
|
||||||
searchQuery = `*${this.state.query.split(' ')[0].substr(1).trim()}*`;
|
searchQuery = `*${this.state.query.split(' ')[0].substr(1).trim()}*`;
|
||||||
results = await Tag.searchAllWithNotes({ titlePattern: searchQuery });
|
results = await Tag.searchAllWithNotes({ titlePattern: searchQuery });
|
||||||
@ -230,7 +298,7 @@ class Dialog extends React.PureComponent {
|
|||||||
searchQuery = this.makeSearchQuery(this.state.query);
|
searchQuery = this.makeSearchQuery(this.state.query);
|
||||||
results = await SearchEngine.instance().search(searchQuery, { fuzzy: this.fuzzy_ });
|
results = await SearchEngine.instance().search(searchQuery, { fuzzy: this.fuzzy_ });
|
||||||
|
|
||||||
resultsInBody = !!results.find(row => row.fields.includes('body'));
|
resultsInBody = !!results.find((row:any) => row.fields.includes('body'));
|
||||||
|
|
||||||
if (!resultsInBody || this.state.query.length <= 1) {
|
if (!resultsInBody || this.state.query.length <= 1) {
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
@ -241,7 +309,9 @@ class Dialog extends React.PureComponent {
|
|||||||
} else {
|
} else {
|
||||||
const limit = 20;
|
const limit = 20;
|
||||||
const searchKeywords = await this.keywords(searchQuery);
|
const searchKeywords = await this.keywords(searchQuery);
|
||||||
const notes = await Note.byIds(results.map(result => result.id).slice(0, limit), { fields: ['id', 'body', 'markup_language', 'is_todo', 'todo_completed'] });
|
const notes = await Note.byIds(results.map((result:any) => result.id).slice(0, limit), { fields: ['id', 'body', 'markup_language', 'is_todo', 'todo_completed'] });
|
||||||
|
// Can't make any sense of this code so...
|
||||||
|
// @ts-ignore
|
||||||
const notesById = notes.reduce((obj, { id, body, markup_language }) => ((obj[[id]] = { id, body, markup_language }), obj), {});
|
const notesById = notes.reduce((obj, { id, body, markup_language }) => ((obj[[id]] = { id, body, markup_language }), obj), {});
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
@ -272,7 +342,7 @@ class Dialog extends React.PureComponent {
|
|||||||
// e.g. 'Joplin is a free, open source' and 'open source note taking application'
|
// e.g. 'Joplin is a free, open source' and 'open source note taking application'
|
||||||
// will result in 'Joplin is a free, open source note taking application'
|
// will result in 'Joplin is a free, open source note taking application'
|
||||||
const mergedIndices = mergeOverlappingIntervals(indices, 3);
|
const mergedIndices = mergeOverlappingIntervals(indices, 3);
|
||||||
fragments = mergedIndices.map(f => body.slice(f[0], f[1])).join(' ... ');
|
fragments = mergedIndices.map((f:any) => body.slice(f[0], f[1])).join(' ... ');
|
||||||
// Add trailing ellipsis if the final fragment doesn't end where the note is ending
|
// Add trailing ellipsis if the final fragment doesn't end where the note is ending
|
||||||
if (mergedIndices.length && mergedIndices[mergedIndices.length - 1][1] !== body.length) fragments += ' ...';
|
if (mergedIndices.length && mergedIndices[mergedIndices.length - 1][1] !== body.length) fragments += ' ...';
|
||||||
|
|
||||||
@ -285,7 +355,7 @@ class Dialog extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.props.showCompletedTodos) {
|
if (!this.props.showCompletedTodos) {
|
||||||
results = results.filter((row) => !row.is_todo || !row.todo_completed);
|
results = results.filter((row:any) => !row.is_todo || !row.todo_completed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,20 +366,25 @@ class Dialog extends React.PureComponent {
|
|||||||
this.setState({
|
this.setState({
|
||||||
listType: listType,
|
listType: listType,
|
||||||
results: results,
|
results: results,
|
||||||
keywords: await this.keywords(searchQuery),
|
keywords: keywords ? keywords : await this.keywords(searchQuery),
|
||||||
selectedItemId: results.length === 0 ? null : results[0].id,
|
selectedItemId: results.length === 0 ? null : results[0].id,
|
||||||
resultsInBody: resultsInBody,
|
resultsInBody: resultsInBody,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async gotoItem(item) {
|
async gotoItem(item:any) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
pluginName: PLUGIN_NAME,
|
pluginName: PLUGIN_NAME,
|
||||||
type: 'PLUGINLEGACY_DIALOG_SET',
|
type: 'PLUGINLEGACY_DIALOG_SET',
|
||||||
open: false,
|
open: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (item.type === BaseModel.TYPE_COMMAND) {
|
||||||
|
CommandService.instance().execute(item.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.listType === BaseModel.TYPE_NOTE || this.state.listType === BaseModel.TYPE_FOLDER) {
|
if (this.state.listType === BaseModel.TYPE_NOTE || this.state.listType === BaseModel.TYPE_FOLDER) {
|
||||||
const folderPath = await Folder.folderPath(this.props.folders, item.parent_id);
|
const folderPath = await Folder.folderPath(this.props.folders, item.parent_id);
|
||||||
|
|
||||||
@ -329,7 +404,7 @@ class Dialog extends React.PureComponent {
|
|||||||
noteId: item.id,
|
noteId: item.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
CommandService.instance().scheduleExecute('focusElement', { target: 'noteBody' });
|
CommandService.instance().scheduleExecute('focusElement', 'noteBody');
|
||||||
} else if (this.state.listType === BaseModel.TYPE_TAG) {
|
} else if (this.state.listType === BaseModel.TYPE_TAG) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'TAG_SELECT',
|
type: 'TAG_SELECT',
|
||||||
@ -343,17 +418,19 @@ class Dialog extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listItem_onClick(event) {
|
listItem_onClick(event:any) {
|
||||||
const itemId = event.currentTarget.getAttribute('data-id');
|
const itemId = event.currentTarget.getAttribute('data-id');
|
||||||
const parentId = event.currentTarget.getAttribute('data-parent-id');
|
const parentId = event.currentTarget.getAttribute('data-parent-id');
|
||||||
|
const itemType = event.currentTarget.getAttribute('data-type');
|
||||||
|
|
||||||
this.gotoItem({
|
this.gotoItem({
|
||||||
id: itemId,
|
id: itemId,
|
||||||
parent_id: parentId,
|
parent_id: parentId,
|
||||||
|
type: itemType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
listItemRenderer(item) {
|
renderItem(item:SearchResult) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = this.style();
|
const style = this.style();
|
||||||
const rowStyle = item.id === this.state.selectedItemId ? style.rowSelected : style.row;
|
const rowStyle = item.id === this.state.selectedItemId ? style.rowSelected : style.row;
|
||||||
@ -368,7 +445,7 @@ class Dialog extends React.PureComponent {
|
|||||||
const fragmentComp = !fragmentsHtml ? null : <div style={style.rowFragments} dangerouslySetInnerHTML={{ __html: (fragmentsHtml) }}></div>;
|
const fragmentComp = !fragmentsHtml ? null : <div style={style.rowFragments} dangerouslySetInnerHTML={{ __html: (fragmentsHtml) }}></div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.id} style={rowStyle} onClick={this.listItem_onClick} data-id={item.id} data-parent-id={item.parent_id}>
|
<div key={item.id} style={rowStyle} onClick={this.listItem_onClick} data-id={item.id} data-parent-id={item.parent_id} data-type={item.type}>
|
||||||
<div style={style.rowTitle} dangerouslySetInnerHTML={{ __html: titleHtml }}></div>
|
<div style={style.rowTitle} dangerouslySetInnerHTML={{ __html: titleHtml }}></div>
|
||||||
{fragmentComp}
|
{fragmentComp}
|
||||||
{pathComp}
|
{pathComp}
|
||||||
@ -376,7 +453,7 @@ class Dialog extends React.PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedItemIndex(results, itemId) {
|
selectedItemIndex(results:any[] = undefined, itemId:string = undefined) {
|
||||||
if (typeof results === 'undefined') results = this.state.results;
|
if (typeof results === 'undefined') results = this.state.results;
|
||||||
if (typeof itemId === 'undefined') itemId = this.state.selectedItemId;
|
if (typeof itemId === 'undefined') itemId = this.state.selectedItemId;
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
@ -392,7 +469,7 @@ class Dialog extends React.PureComponent {
|
|||||||
return this.state.results[index];
|
return this.state.results[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
input_onKeyDown(event) {
|
input_onKeyDown(event:any) {
|
||||||
const keyCode = event.keyCode;
|
const keyCode = event.keyCode;
|
||||||
|
|
||||||
if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
|
if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
|
||||||
@ -428,7 +505,7 @@ class Dialog extends React.PureComponent {
|
|||||||
|
|
||||||
const itemListStyle = {
|
const itemListStyle = {
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
height: Math.min(style.itemHeight * this.state.results.length, 7 * style.itemHeight),
|
height: Math.min(style.itemHeight * this.state.results.length, 10 * style.itemHeight),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -437,7 +514,7 @@ class Dialog extends React.PureComponent {
|
|||||||
itemHeight={style.itemHeight}
|
itemHeight={style.itemHeight}
|
||||||
items={this.state.results}
|
items={this.state.results}
|
||||||
style={itemListStyle}
|
style={itemListStyle}
|
||||||
itemRenderer={this.listItemRenderer}
|
itemRenderer={this.renderItem}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -445,7 +522,7 @@ class Dialog extends React.PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = this.style();
|
const style = this.style();
|
||||||
const helpComp = !this.state.showHelp ? null : <div style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name.')}</div>;
|
const helpComp = !this.state.showHelp ? null : <div style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={this.modalLayer_onClick} style={theme.dialogModalLayer}>
|
<div onClick={this.modalLayer_onClick} style={theme.dialogModalLayer}>
|
||||||
@ -463,7 +540,7 @@ class Dialog extends React.PureComponent {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state:AppState) => {
|
||||||
return {
|
return {
|
||||||
folders: state.folders,
|
folders: state.folders,
|
||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
@ -485,8 +562,18 @@ GotoAnything.manifest = {
|
|||||||
accelerator: () => KeymapService.instance().getAccelerator('gotoAnything'),
|
accelerator: () => KeymapService.instance().getAccelerator('gotoAnything'),
|
||||||
screens: ['Main'],
|
screens: ['Main'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'main',
|
||||||
|
parent: 'tools',
|
||||||
|
label: _('Command palette'),
|
||||||
|
accelerator: () => KeymapService.instance().getAccelerator('commandPalette'),
|
||||||
|
screens: ['Main'],
|
||||||
|
userData: {
|
||||||
|
startString: ':',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = GotoAnything;
|
export default GotoAnything;
|
5
ElectronClient/services/commands/types.ts
Normal file
5
ElectronClient/services/commands/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { AppState } from '../../app';
|
||||||
|
|
||||||
|
export interface DesktopCommandContext {
|
||||||
|
state: AppState,
|
||||||
|
}
|
@ -590,7 +590,24 @@ class BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseModel.typeEnum_ = [['TYPE_NOTE', 1], ['TYPE_FOLDER', 2], ['TYPE_SETTING', 3], ['TYPE_RESOURCE', 4], ['TYPE_TAG', 5], ['TYPE_NOTE_TAG', 6], ['TYPE_SEARCH', 7], ['TYPE_ALARM', 8], ['TYPE_MASTER_KEY', 9], ['TYPE_ITEM_CHANGE', 10], ['TYPE_NOTE_RESOURCE', 11], ['TYPE_RESOURCE_LOCAL_STATE', 12], ['TYPE_REVISION', 13], ['TYPE_MIGRATION', 14], ['TYPE_SMART_FILTER', 15]];
|
BaseModel.typeEnum_ = [
|
||||||
|
['TYPE_NOTE', 1],
|
||||||
|
['TYPE_FOLDER', 2],
|
||||||
|
['TYPE_SETTING', 3],
|
||||||
|
['TYPE_RESOURCE', 4],
|
||||||
|
['TYPE_TAG', 5],
|
||||||
|
['TYPE_NOTE_TAG', 6],
|
||||||
|
['TYPE_SEARCH', 7],
|
||||||
|
['TYPE_ALARM', 8],
|
||||||
|
['TYPE_MASTER_KEY', 9],
|
||||||
|
['TYPE_ITEM_CHANGE', 10],
|
||||||
|
['TYPE_NOTE_RESOURCE', 11],
|
||||||
|
['TYPE_RESOURCE_LOCAL_STATE', 12],
|
||||||
|
['TYPE_REVISION', 13],
|
||||||
|
['TYPE_MIGRATION', 14],
|
||||||
|
['TYPE_SMART_FILTER', 15],
|
||||||
|
['TYPE_COMMAND', 16],
|
||||||
|
];
|
||||||
|
|
||||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||||
const e = BaseModel.typeEnum_[i];
|
const e = BaseModel.typeEnum_[i];
|
||||||
|
@ -4,27 +4,16 @@ import { _ } from 'lib/locale';
|
|||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'historyBackward',
|
name: 'historyBackward',
|
||||||
label: () => _('Back'),
|
label: () => _('Back'),
|
||||||
// iconName: 'fa-arrow-left',
|
|
||||||
iconName: 'icon-back',
|
iconName: 'icon-back',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
|
||||||
hasBackwardNotes: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async (props:Props) => {
|
execute: async () => {
|
||||||
if (!props.hasBackwardNotes) return;
|
|
||||||
utils.store.dispatch({
|
utils.store.dispatch({
|
||||||
type: 'HISTORY_BACKWARD',
|
type: 'HISTORY_BACKWARD',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEnabled: (props:Props) => {
|
enabledCondition: 'historyhasBackwardNotes',
|
||||||
return props.hasBackwardNotes;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return { hasBackwardNotes: state.backwardHistoryNotes.length > 0 };
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -7,23 +7,13 @@ export const declaration:CommandDeclaration = {
|
|||||||
iconName: 'icon-forward',
|
iconName: 'icon-forward',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
|
||||||
hasForwardNotes: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async (props:Props) => {
|
execute: async () => {
|
||||||
if (!props.hasForwardNotes) return;
|
|
||||||
utils.store.dispatch({
|
utils.store.dispatch({
|
||||||
type: 'HISTORY_FORWARD',
|
type: 'HISTORY_FORWARD',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEnabled: (props:Props) => {
|
enabledCondition: 'historyhasForwardNotes',
|
||||||
return props.hasForwardNotes;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any) => {
|
|
||||||
return { hasForwardNotes: state.forwardHistoryNotes.length > 0 };
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { utils, CommandRuntime, CommandDeclaration } from '../services/CommandService';
|
import { utils, CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
|
|
||||||
@ -8,9 +8,11 @@ export const declaration:CommandDeclaration = {
|
|||||||
iconName: 'fa-sync-alt',
|
iconName: 'fa-sync-alt',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Note that this command actually acts as a toggle - it starts or cancels
|
||||||
|
// synchronisation depending on the "syncStarted" parameter
|
||||||
export const runtime = ():CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async ({ syncStarted }:any) => {
|
execute: async (_context:CommandContext, syncStarted:boolean = false) => {
|
||||||
const action = syncStarted ? 'cancel' : 'start';
|
const action = syncStarted ? 'cancel' : 'start';
|
||||||
|
|
||||||
if (!(await reg.syncTarget().isAuthenticated())) {
|
if (!(await reg.syncTarget().isAuthenticated())) {
|
||||||
@ -43,13 +45,5 @@ export const runtime = ():CommandRuntime => {
|
|||||||
return 'sync';
|
return 'sync';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEnabled: (props:any) => {
|
|
||||||
return !props.syncStarted;
|
|
||||||
},
|
|
||||||
mapStateToProps: (state:any):any => {
|
|
||||||
return {
|
|
||||||
syncStarted: state.syncStarted,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import produce, { Draft } from 'immer';
|
|||||||
import pluginServiceReducer, { stateRootKey as pluginServiceStateRootKey, defaultState as pluginServiceDefaultState, State as PluginServiceState } from 'lib/services/plugins/reducer';
|
import pluginServiceReducer, { stateRootKey as pluginServiceStateRootKey, defaultState as pluginServiceDefaultState, State as PluginServiceState } from 'lib/services/plugins/reducer';
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
|
const BaseModel = require('lib/BaseModel');
|
||||||
const ArrayUtils = require('lib/ArrayUtils.js');
|
const ArrayUtils = require('lib/ArrayUtils.js');
|
||||||
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
||||||
const { createSelectorCreator, defaultMemoize } = require('reselect');
|
const { createSelectorCreator, defaultMemoize } = require('reselect');
|
||||||
@ -160,8 +161,6 @@ for (const additionalReducer of additionalReducers) {
|
|||||||
|
|
||||||
export const MAX_HISTORY = 200;
|
export const MAX_HISTORY = 200;
|
||||||
|
|
||||||
export const stateUtils:any = {};
|
|
||||||
|
|
||||||
const derivedStateCache_:any = {};
|
const derivedStateCache_:any = {};
|
||||||
|
|
||||||
// Allows, for a given state, to return the same derived
|
// Allows, for a given state, to return the same derived
|
||||||
@ -185,9 +184,7 @@ const createShallowArrayEqualSelector = createSelectorCreator(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Given an input array, this selector ensures that the same array is returned
|
const selectArrayShallow = createCachedSelector(
|
||||||
// if its content hasn't changed.
|
|
||||||
stateUtils.selectArrayShallow = createCachedSelector(
|
|
||||||
(state:any) => state.array,
|
(state:any) => state.array,
|
||||||
(array:any[]) => array
|
(array:any[]) => array
|
||||||
)({
|
)({
|
||||||
@ -197,85 +194,85 @@ stateUtils.selectArrayShallow = createCachedSelector(
|
|||||||
selectorCreator: createShallowArrayEqualSelector,
|
selectorCreator: createShallowArrayEqualSelector,
|
||||||
});
|
});
|
||||||
|
|
||||||
stateUtils.hasOneSelectedNote = function(state:State):boolean {
|
class StateUtils {
|
||||||
return state.selectedNoteIds.length === 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
stateUtils.notesOrder = function(stateSettings:any) {
|
// Given an input array, this selector ensures that the same array is returned
|
||||||
if (stateSettings['notes.sortOrder.field'] === 'order') {
|
// if its content hasn't changed.
|
||||||
return cacheEnabledOutput('notesOrder', [
|
public selectArrayShallow(props:any, cacheKey:any) {
|
||||||
{
|
return selectArrayShallow(props, cacheKey);
|
||||||
by: 'order',
|
|
||||||
dir: 'DESC',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
by: 'user_created_time',
|
|
||||||
dir: 'DESC',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
return cacheEnabledOutput('notesOrder', [
|
|
||||||
{
|
|
||||||
by: stateSettings['notes.sortOrder.field'],
|
|
||||||
dir: stateSettings['notes.sortOrder.reverse'] ? 'DESC' : 'ASC',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
stateUtils.foldersOrder = function(stateSettings:any) {
|
public oneNoteSelected(state:State):boolean {
|
||||||
return cacheEnabledOutput('foldersOrder', [
|
return state.selectedNoteIds.length === 1;
|
||||||
{
|
|
||||||
by: stateSettings['folders.sortOrder.field'],
|
|
||||||
dir: stateSettings['folders.sortOrder.reverse'] ? 'DESC' : 'ASC',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
stateUtils.hasNotesBeingSaved = function(state:State):boolean {
|
|
||||||
for (const id in state.editorNoteStatuses) {
|
|
||||||
if (state.editorNoteStatuses[id] === 'saving') return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
stateUtils.parentItem = function(state:State) {
|
public notesOrder(stateSettings:any) {
|
||||||
const t = state.notesParentType;
|
if (stateSettings['notes.sortOrder.field'] === 'order') {
|
||||||
let id = null;
|
return cacheEnabledOutput('notesOrder', [
|
||||||
if (t === 'Folder') id = state.selectedFolderId;
|
{
|
||||||
if (t === 'Tag') id = state.selectedTagId;
|
by: 'order',
|
||||||
if (t === 'Search') id = state.selectedSearchId;
|
dir: 'DESC',
|
||||||
if (!t || !id) return null;
|
},
|
||||||
return { type: t, id: id };
|
{
|
||||||
};
|
by: 'user_created_time',
|
||||||
|
dir: 'DESC',
|
||||||
stateUtils.lastSelectedNoteIds = function(state:State):string[] {
|
},
|
||||||
const parent = stateUtils.parentItem(state);
|
]);
|
||||||
if (!parent) return [];
|
} else {
|
||||||
const output = (state.lastSelectedNotesIds as any)[parent.type][parent.id];
|
return cacheEnabledOutput('notesOrder', [
|
||||||
return output ? output : [];
|
{
|
||||||
};
|
by: stateSettings['notes.sortOrder.field'],
|
||||||
|
dir: stateSettings['notes.sortOrder.reverse'] ? 'DESC' : 'ASC',
|
||||||
stateUtils.getCurrentNote = function(state:State) {
|
},
|
||||||
const selectedNoteIds = state.selectedNoteIds;
|
]);
|
||||||
const notes = state.notes;
|
|
||||||
if (selectedNoteIds != null && selectedNoteIds.length > 0) {
|
|
||||||
const currNote = notes.find(note => note.id === selectedNoteIds[0]);
|
|
||||||
if (currNote != null) {
|
|
||||||
return {
|
|
||||||
id: currNote.id,
|
|
||||||
parent_id: currNote.parent_id,
|
|
||||||
notesParentType: state.notesParentType,
|
|
||||||
selectedFolderId: state.selectedFolderId,
|
|
||||||
selectedTagId: state.selectedTagId,
|
|
||||||
selectedSearchId: state.selectedSearchId,
|
|
||||||
searches: state.searches,
|
|
||||||
selectedSmartFilterId: state.selectedSmartFilterId,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
};
|
public foldersOrder(stateSettings:any) {
|
||||||
|
return cacheEnabledOutput('foldersOrder', [
|
||||||
|
{
|
||||||
|
by: stateSettings['folders.sortOrder.field'],
|
||||||
|
dir: stateSettings['folders.sortOrder.reverse'] ? 'DESC' : 'ASC',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasNotesBeingSaved(state:State):boolean {
|
||||||
|
for (const id in state.editorNoteStatuses) {
|
||||||
|
if (state.editorNoteStatuses[id] === 'saving') return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parentItem(state:State) {
|
||||||
|
const t = state.notesParentType;
|
||||||
|
let id = null;
|
||||||
|
if (t === 'Folder') id = state.selectedFolderId;
|
||||||
|
if (t === 'Tag') id = state.selectedTagId;
|
||||||
|
if (t === 'Search') id = state.selectedSearchId;
|
||||||
|
if (!t || !id) return null;
|
||||||
|
return { type: t, id: id };
|
||||||
|
}
|
||||||
|
|
||||||
|
public lastSelectedNoteIds(state:State):string[] {
|
||||||
|
const parent = this.parentItem(state);
|
||||||
|
if (!parent) return [];
|
||||||
|
const output = (state.lastSelectedNotesIds as any)[parent.type][parent.id];
|
||||||
|
return output ? output : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectedNote(state:State):any {
|
||||||
|
const noteId = this.selectedNoteId(state);
|
||||||
|
return noteId ? BaseModel.byId(state.notes, noteId) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectedNoteId(state:State):any {
|
||||||
|
return state.selectedNoteIds.length ? state.selectedNoteIds[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stateUtils:StateUtils = new StateUtils();
|
||||||
|
|
||||||
function arrayHasEncryptedItems(array:any[]) {
|
function arrayHasEncryptedItems(array:any[]) {
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
@ -526,8 +523,29 @@ const getContextFromHistory = (ctx:any) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getNoteHistoryInfo(state:State) {
|
||||||
|
const selectedNoteIds = state.selectedNoteIds;
|
||||||
|
const notes = state.notes;
|
||||||
|
if (selectedNoteIds != null && selectedNoteIds.length > 0) {
|
||||||
|
const currNote = notes.find(note => note.id === selectedNoteIds[0]);
|
||||||
|
if (currNote != null) {
|
||||||
|
return {
|
||||||
|
id: currNote.id,
|
||||||
|
parent_id: currNote.parent_id,
|
||||||
|
notesParentType: state.notesParentType,
|
||||||
|
selectedFolderId: state.selectedFolderId,
|
||||||
|
selectedTagId: state.selectedTagId,
|
||||||
|
selectedSearchId: state.selectedSearchId,
|
||||||
|
searches: state.searches,
|
||||||
|
selectedSmartFilterId: state.selectedSmartFilterId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function handleHistory(draft:Draft<State>, action:any) {
|
function handleHistory(draft:Draft<State>, action:any) {
|
||||||
const currentNote = stateUtils.getCurrentNote(draft);
|
const currentNote = getNoteHistoryInfo(draft);
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'HISTORY_BACKWARD': {
|
case 'HISTORY_BACKWARD': {
|
||||||
const note = draft.backwardHistoryNotes[draft.backwardHistoryNotes.length - 1];
|
const note = draft.backwardHistoryNotes[draft.backwardHistoryNotes.length - 1];
|
||||||
@ -1086,6 +1104,7 @@ const reducer = produce((draft: Draft<State> = defaultState, action:any) => {
|
|||||||
const newPluginsLegacy = Object.assign({}, draft.pluginsLegacy);
|
const newPluginsLegacy = Object.assign({}, draft.pluginsLegacy);
|
||||||
const newPlugin = draft.pluginsLegacy[action.pluginName] ? Object.assign({}, draft.pluginsLegacy[action.pluginName]) : {};
|
const newPlugin = draft.pluginsLegacy[action.pluginName] ? Object.assign({}, draft.pluginsLegacy[action.pluginName]) : {};
|
||||||
if ('open' in action) newPlugin.dialogOpen = action.open;
|
if ('open' in action) newPlugin.dialogOpen = action.open;
|
||||||
|
if ('userData' in action) newPlugin.userData = action.userData;
|
||||||
newPluginsLegacy[action.pluginName] = newPlugin;
|
newPluginsLegacy[action.pluginName] = newPlugin;
|
||||||
draft.pluginsLegacy = newPluginsLegacy;
|
draft.pluginsLegacy = newPluginsLegacy;
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,23 @@
|
|||||||
|
import { State } from 'lib/reducer';
|
||||||
import eventManager from 'lib/eventManager';
|
import eventManager from 'lib/eventManager';
|
||||||
import markdownUtils, { MarkdownTableHeader, MarkdownTableRow } from 'lib/markdownUtils';
|
|
||||||
import BaseService from 'lib/services/BaseService';
|
import BaseService from 'lib/services/BaseService';
|
||||||
import shim from 'lib/shim';
|
import shim from 'lib/shim';
|
||||||
|
import WhenClause from './WhenClause';
|
||||||
|
import stateToWhenClauseContext from './commands/stateToWhenClauseContext';
|
||||||
|
|
||||||
type LabelFunction = () => string;
|
type LabelFunction = () => string;
|
||||||
|
type EnabledCondition = string;
|
||||||
|
|
||||||
|
export interface CommandContext {
|
||||||
|
// The state may also be of type "AppState" (used by the desktop app), which inherits from "State" (used by all apps)
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
export interface CommandRuntime {
|
export interface CommandRuntime {
|
||||||
execute(props:any):Promise<any>
|
execute(context:CommandContext, ...args:any[]):Promise<any>
|
||||||
isEnabled?(props:any):boolean
|
enabledCondition?: EnabledCondition;
|
||||||
|
|
||||||
// "state" type is "AppState" but in order not to introduce a
|
|
||||||
// dependency to the desktop app (so that the service can
|
|
||||||
// potentially be used by the mobile app too), we keep it as "any".
|
|
||||||
// Individual commands can define it as state:AppState when relevant.
|
|
||||||
//
|
|
||||||
// In general this method should reduce the provided state to only
|
|
||||||
// what's absolutely necessary. For example, if the property of a
|
|
||||||
// note is needed, return only that particular property and not the
|
|
||||||
// whole note object. This will ensure that components that depends
|
|
||||||
// on this command are not uncessarily re-rendered. A note object for
|
|
||||||
// example might change frequently but its markdown_language property
|
|
||||||
// will almost never change.
|
|
||||||
mapStateToProps?(state:any):any
|
|
||||||
|
|
||||||
// Used for the (optional) toolbar button title
|
// Used for the (optional) toolbar button title
|
||||||
title?(props:any):string,
|
mapStateToTitle?(state:any):string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommandDeclaration {
|
export interface CommandDeclaration {
|
||||||
@ -33,6 +26,9 @@ export interface CommandDeclaration {
|
|||||||
// Used for the menu item label, and toolbar button tooltip
|
// Used for the menu item label, and toolbar button tooltip
|
||||||
label?: LabelFunction | string,
|
label?: LabelFunction | string,
|
||||||
|
|
||||||
|
// Command description - if none is provided, the label will be used as description
|
||||||
|
description?: string,
|
||||||
|
|
||||||
// This is a bit of a hack because some labels don't make much sense in isolation. For example,
|
// This is a bit of a hack because some labels don't make much sense in isolation. For example,
|
||||||
// the commmand to focus the note list is called just "Note list". This makes sense within the menu
|
// the commmand to focus the note list is called just "Note list". This makes sense within the menu
|
||||||
// but not so much within the keymap config screen, where the parent item is not displayed. Because
|
// but not so much within the keymap config screen, where the parent item is not displayed. Because
|
||||||
@ -88,30 +84,29 @@ interface CommandByNameOptions {
|
|||||||
runtimeMustBeRegistered?:boolean,
|
runtimeMustBeRegistered?:boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommandState {
|
export interface SearchResult {
|
||||||
|
commandName: string,
|
||||||
title: string,
|
title: string,
|
||||||
enabled: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CommandStates {
|
|
||||||
[key:string]: CommandState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CommandService extends BaseService {
|
export default class CommandService extends BaseService {
|
||||||
|
|
||||||
private static instance_:CommandService;
|
private static instance_:CommandService;
|
||||||
|
|
||||||
static instance():CommandService {
|
public static instance():CommandService {
|
||||||
if (this.instance_) return this.instance_;
|
if (this.instance_) return this.instance_;
|
||||||
this.instance_ = new CommandService();
|
this.instance_ = new CommandService();
|
||||||
return this.instance_;
|
return this.instance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private commands_:Commands = {};
|
private commands_:Commands = {};
|
||||||
private commandPreviousStates_:CommandStates = {};
|
private store_:any;
|
||||||
|
private devMode_:boolean;
|
||||||
|
|
||||||
initialize(store:any) {
|
public initialize(store:any, devMode:boolean) {
|
||||||
utils.store = store;
|
utils.store = store;
|
||||||
|
this.store_ = store;
|
||||||
|
this.devMode_ = devMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public on(eventName:string, callback:Function) {
|
public on(eventName:string, callback:Function) {
|
||||||
@ -122,6 +117,36 @@ export default class CommandService extends BaseService {
|
|||||||
eventManager.off(eventName, callback);
|
eventManager.off(eventName, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public searchCommands(query:string, returnAllWhenEmpty:boolean, excludeWithoutLabel:boolean = true):SearchResult[] {
|
||||||
|
query = query.toLowerCase();
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (const commandName of this.commandNames()) {
|
||||||
|
const label = this.label(commandName, true);
|
||||||
|
if (!label && excludeWithoutLabel) continue;
|
||||||
|
|
||||||
|
const title = label ? `${label} (${commandName})` : commandName;
|
||||||
|
|
||||||
|
if ((returnAllWhenEmpty && !query) || title.toLowerCase().includes(query)) {
|
||||||
|
output.push({
|
||||||
|
commandName: commandName,
|
||||||
|
title: title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.sort((a:SearchResult, b:SearchResult) => {
|
||||||
|
return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public commandNames() {
|
||||||
|
return Object.keys(this.commands_);
|
||||||
|
}
|
||||||
|
|
||||||
public commandByName(name:string, options:CommandByNameOptions = null):Command {
|
public commandByName(name:string, options:CommandByNameOptions = null):Command {
|
||||||
options = {
|
options = {
|
||||||
mustExist: true,
|
mustExist: true,
|
||||||
@ -140,7 +165,7 @@ export default class CommandService extends BaseService {
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerDeclaration(declaration:CommandDeclaration) {
|
public registerDeclaration(declaration:CommandDeclaration) {
|
||||||
declaration = { ...declaration };
|
declaration = { ...declaration };
|
||||||
if (!declaration.label) declaration.label = '';
|
if (!declaration.label) declaration.label = '';
|
||||||
if (!declaration.iconName) declaration.iconName = '';
|
if (!declaration.iconName) declaration.iconName = '';
|
||||||
@ -148,83 +173,89 @@ export default class CommandService extends BaseService {
|
|||||||
this.commands_[declaration.name] = {
|
this.commands_[declaration.name] = {
|
||||||
declaration: declaration,
|
declaration: declaration,
|
||||||
};
|
};
|
||||||
|
|
||||||
delete this.commandPreviousStates_[declaration.name];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerRuntime(commandName:string, runtime:CommandRuntime) {
|
public registerRuntime(commandName:string, runtime:CommandRuntime) {
|
||||||
if (typeof commandName !== 'string') throw new Error(`Command name must be a string. Got: ${JSON.stringify(commandName)}`);
|
if (typeof commandName !== 'string') throw new Error(`Command name must be a string. Got: ${JSON.stringify(commandName)}`);
|
||||||
|
|
||||||
const command = this.commandByName(commandName);
|
const command = this.commandByName(commandName);
|
||||||
|
|
||||||
runtime = Object.assign({}, runtime);
|
runtime = Object.assign({}, runtime);
|
||||||
if (!runtime.isEnabled) runtime.isEnabled = () => true;
|
if (!runtime.enabledCondition) runtime.enabledCondition = 'true';
|
||||||
if (!runtime.title) runtime.title = () => null;
|
|
||||||
command.runtime = runtime;
|
command.runtime = runtime;
|
||||||
|
|
||||||
delete this.commandPreviousStates_[commandName];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentRegisterCommands(component:any, commands:any[]) {
|
public componentRegisterCommands(component:any, commands:any[]) {
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(component));
|
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(component));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentUnregisterCommands(commands:any[]) {
|
public componentUnregisterCommands(commands:any[]) {
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
CommandService.instance().unregisterRuntime(command.declaration.name);
|
CommandService.instance().unregisterRuntime(command.declaration.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterRuntime(commandName:string) {
|
public unregisterRuntime(commandName:string) {
|
||||||
const command = this.commandByName(commandName, { mustExist: false });
|
const command = this.commandByName(commandName, { mustExist: false });
|
||||||
if (!command || !command.runtime) return;
|
if (!command || !command.runtime) return;
|
||||||
delete command.runtime;
|
delete command.runtime;
|
||||||
|
|
||||||
delete this.commandPreviousStates_[commandName];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(commandName:string, props:any = null):Promise<any> {
|
public async execute(commandName:string, ...args:any[]):Promise<any> {
|
||||||
const command = this.commandByName(commandName);
|
const command = this.commandByName(commandName);
|
||||||
this.logger().info('CommandService::execute:', commandName, props);
|
this.logger().info('CommandService::execute:', commandName, args);
|
||||||
return command.runtime.execute(props ? props : {});
|
return command.runtime.execute({ state: this.store_.getState() }, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleExecute(commandName:string, args:any) {
|
public scheduleExecute(commandName:string, args:any) {
|
||||||
shim.setTimeout(() => {
|
shim.setTimeout(() => {
|
||||||
this.execute(commandName, args);
|
this.execute(commandName, args);
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled(commandName:string, props:any):boolean {
|
public currentWhenClauseContext() {
|
||||||
|
return stateToWhenClauseContext(this.store_.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// When looping on commands and checking their enabled state, the whenClauseContext
|
||||||
|
// should be specified (created using currentWhenClauseContext) to avoid having
|
||||||
|
// to re-create it on each call.
|
||||||
|
public isEnabled(commandName:string, whenClauseContext:any = null):boolean {
|
||||||
const command = this.commandByName(commandName);
|
const command = this.commandByName(commandName);
|
||||||
if (!command || !command.runtime) return false;
|
if (!command || !command.runtime) return false;
|
||||||
// if (!command.runtime.props) return false;
|
|
||||||
return command.runtime.isEnabled(props);
|
if (!whenClauseContext) whenClauseContext = this.currentWhenClauseContext();
|
||||||
|
|
||||||
|
const exp = new WhenClause(command.runtime.enabledCondition, this.devMode_);
|
||||||
|
return exp.evaluate(whenClauseContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMapStateToProps(commandName:string, state:any):any {
|
// The title is dynamic and derived from the state, which is why the state is passed
|
||||||
const command = this.commandByName(commandName);
|
// as an argument. Title can be used for example to display the alarm date on the
|
||||||
if (!command.runtime) return null;
|
// "set alarm" toolbar button.
|
||||||
if (!command.runtime.mapStateToProps) return {};
|
public title(commandName:string, state:any = null):string {
|
||||||
return command.runtime.mapStateToProps(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
title(commandName:string, props:any):string {
|
|
||||||
const command = this.commandByName(commandName);
|
const command = this.commandByName(commandName);
|
||||||
if (!command || !command.runtime) return null;
|
if (!command || !command.runtime) return null;
|
||||||
return command.runtime.title(props);
|
|
||||||
|
state = state || this.store_.getState();
|
||||||
|
|
||||||
|
if (command.runtime.mapStateToTitle) {
|
||||||
|
return command.runtime.mapStateToTitle(state);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iconName(commandName:string, variant:string = null):string {
|
public iconName(commandName:string, variant:string = null):string {
|
||||||
const command = this.commandByName(commandName);
|
const command = this.commandByName(commandName);
|
||||||
if (!command) throw new Error(`No such command: ${commandName}`);
|
if (!command) throw new Error(`No such command: ${commandName}`);
|
||||||
if (variant === 'tinymce') return command.declaration.tinymceIconName ? command.declaration.tinymceIconName : 'preferences';
|
if (variant === 'tinymce') return command.declaration.tinymceIconName ? command.declaration.tinymceIconName : 'preferences';
|
||||||
return command.declaration.iconName;
|
return command.declaration.iconName;
|
||||||
}
|
}
|
||||||
|
|
||||||
label(commandName:string, fullLabel:boolean = false):string {
|
public label(commandName:string, fullLabel:boolean = false):string {
|
||||||
const command = this.commandByName(commandName);
|
const command = this.commandByName(commandName);
|
||||||
if (!command) throw new Error(`Command: ${commandName} is not declared`);
|
if (!command) throw new Error(`Command: ${commandName} is not declared`);
|
||||||
const output = [];
|
const output = [];
|
||||||
@ -240,42 +271,15 @@ export default class CommandService extends BaseService {
|
|||||||
return output.join(': ');
|
return output.join(': ');
|
||||||
}
|
}
|
||||||
|
|
||||||
exists(commandName:string):boolean {
|
public description(commandName:string):string {
|
||||||
|
const command = this.commandByName(commandName);
|
||||||
|
if (command.declaration.description) return command.declaration.description;
|
||||||
|
return this.label(commandName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public exists(commandName:string):boolean {
|
||||||
const command = this.commandByName(commandName, { mustExist: false });
|
const command = this.commandByName(commandName, { mustExist: false });
|
||||||
return !!command;
|
return !!command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public commandsToMarkdownTable(state:any):string {
|
|
||||||
const headers:MarkdownTableHeader[] = [
|
|
||||||
{
|
|
||||||
name: 'commandName',
|
|
||||||
label: 'Name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'description',
|
|
||||||
label: 'Description',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'props',
|
|
||||||
label: 'Props',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const rows:MarkdownTableRow[] = [];
|
|
||||||
|
|
||||||
for (const commandName in this.commands_) {
|
|
||||||
const props = this.commandMapStateToProps(commandName, state);
|
|
||||||
|
|
||||||
const row:MarkdownTableRow = {
|
|
||||||
commandName: commandName,
|
|
||||||
description: this.label(commandName),
|
|
||||||
props: JSON.stringify(props),
|
|
||||||
};
|
|
||||||
|
|
||||||
rows.push(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
return markdownUtils.createMarkdownTable(headers, rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ const defaultKeymapItems = {
|
|||||||
{ accelerator: 'Cmd+N', command: 'newNote' },
|
{ accelerator: 'Cmd+N', command: 'newNote' },
|
||||||
{ accelerator: 'Cmd+T', command: 'newTodo' },
|
{ accelerator: 'Cmd+T', command: 'newTodo' },
|
||||||
{ accelerator: 'Cmd+S', command: 'synchronize' },
|
{ accelerator: 'Cmd+S', command: 'synchronize' },
|
||||||
{ accelerator: 'Cmd+P', command: 'print' },
|
{ accelerator: '', command: 'print' },
|
||||||
{ accelerator: 'Cmd+H', command: 'hideApp' },
|
{ accelerator: 'Cmd+H', command: 'hideApp' },
|
||||||
{ accelerator: 'Cmd+Q', command: 'quit' },
|
{ accelerator: 'Cmd+Q', command: 'quit' },
|
||||||
{ accelerator: 'Cmd+,', command: 'config' },
|
{ accelerator: 'Cmd+,', command: 'config' },
|
||||||
@ -37,20 +37,21 @@ const defaultKeymapItems = {
|
|||||||
{ accelerator: 'Shift+Cmd+L', command: 'focusElementNoteList' },
|
{ accelerator: 'Shift+Cmd+L', command: 'focusElementNoteList' },
|
||||||
{ accelerator: 'Shift+Cmd+N', command: 'focusElementNoteTitle' },
|
{ accelerator: 'Shift+Cmd+N', command: 'focusElementNoteTitle' },
|
||||||
{ accelerator: 'Shift+Cmd+B', command: 'focusElementNoteBody' },
|
{ accelerator: 'Shift+Cmd+B', command: 'focusElementNoteBody' },
|
||||||
{ accelerator: 'Option+Cmd+S', command: 'toggleSidebar' },
|
{ accelerator: 'Option+Cmd+S', command: 'toggleSideBar' },
|
||||||
{ accelerator: 'Option+Cmd+L', command: 'toggleNoteList' },
|
{ accelerator: 'Option+Cmd+L', command: 'toggleNoteList' },
|
||||||
{ accelerator: 'Cmd+L', command: 'toggleVisiblePanes' },
|
{ accelerator: 'Cmd+L', command: 'toggleVisiblePanes' },
|
||||||
{ accelerator: 'Cmd+0', command: 'zoomActualSize' },
|
{ accelerator: 'Cmd+0', command: 'zoomActualSize' },
|
||||||
{ accelerator: 'Cmd+E', command: 'toggleExternalEditing' },
|
{ accelerator: 'Cmd+E', command: 'toggleExternalEditing' },
|
||||||
{ accelerator: 'Option+Cmd+T', command: 'setTags' },
|
{ accelerator: 'Option+Cmd+T', command: 'setTags' },
|
||||||
{ accelerator: 'Cmd+G', command: 'gotoAnything' },
|
{ accelerator: 'Cmd+P', command: 'gotoAnything' },
|
||||||
|
{ accelerator: 'Shift+Cmd+P', command: 'commandPalette' },
|
||||||
{ accelerator: 'F1', command: 'help' },
|
{ accelerator: 'F1', command: 'help' },
|
||||||
],
|
],
|
||||||
default: [
|
default: [
|
||||||
{ accelerator: 'Ctrl+N', command: 'newNote' },
|
{ accelerator: 'Ctrl+N', command: 'newNote' },
|
||||||
{ accelerator: 'Ctrl+T', command: 'newTodo' },
|
{ accelerator: 'Ctrl+T', command: 'newTodo' },
|
||||||
{ accelerator: 'Ctrl+S', command: 'synchronize' },
|
{ accelerator: 'Ctrl+S', command: 'synchronize' },
|
||||||
{ accelerator: 'Ctrl+P', command: 'print' },
|
{ accelerator: '', command: 'print' },
|
||||||
{ accelerator: 'Ctrl+Q', command: 'quit' },
|
{ accelerator: 'Ctrl+Q', command: 'quit' },
|
||||||
{ accelerator: 'Ctrl+Alt+I', command: 'insertTemplate' },
|
{ accelerator: 'Ctrl+Alt+I', command: 'insertTemplate' },
|
||||||
{ accelerator: 'Ctrl+C', command: 'textCopy' },
|
{ accelerator: 'Ctrl+C', command: 'textCopy' },
|
||||||
@ -68,14 +69,15 @@ const defaultKeymapItems = {
|
|||||||
{ accelerator: 'Ctrl+Shift+L', command: 'focusElementNoteList' },
|
{ accelerator: 'Ctrl+Shift+L', command: 'focusElementNoteList' },
|
||||||
{ accelerator: 'Ctrl+Shift+N', command: 'focusElementNoteTitle' },
|
{ accelerator: 'Ctrl+Shift+N', command: 'focusElementNoteTitle' },
|
||||||
{ accelerator: 'Ctrl+Shift+B', command: 'focusElementNoteBody' },
|
{ accelerator: 'Ctrl+Shift+B', command: 'focusElementNoteBody' },
|
||||||
{ accelerator: 'F10', command: 'toggleSidebar' },
|
{ accelerator: 'F10', command: 'toggleSideBar' },
|
||||||
{ accelerator: 'F11', command: 'toggleNoteList' },
|
{ accelerator: 'F11', command: 'toggleNoteList' },
|
||||||
{ accelerator: 'Ctrl+L', command: 'toggleVisiblePanes' },
|
{ accelerator: 'Ctrl+L', command: 'toggleVisiblePanes' },
|
||||||
{ accelerator: 'Ctrl+0', command: 'zoomActualSize' },
|
{ accelerator: 'Ctrl+0', command: 'zoomActualSize' },
|
||||||
{ accelerator: 'Ctrl+E', command: 'toggleExternalEditing' },
|
{ accelerator: 'Ctrl+E', command: 'toggleExternalEditing' },
|
||||||
{ accelerator: 'Ctrl+Alt+T', command: 'setTags' },
|
{ accelerator: 'Ctrl+Alt+T', command: 'setTags' },
|
||||||
{ accelerator: 'Ctrl+,', command: 'config' },
|
{ accelerator: 'Ctrl+,', command: 'config' },
|
||||||
{ accelerator: 'Ctrl+G', command: 'gotoAnything' },
|
{ accelerator: 'Ctrl+P', command: 'gotoAnything' },
|
||||||
|
{ accelerator: 'Ctrl+Shift+P', command: 'commandPalette' },
|
||||||
{ accelerator: 'F1', command: 'help' },
|
{ accelerator: 'F1', command: 'help' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -90,13 +92,14 @@ interface Keymap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class KeymapService extends BaseService {
|
export default class KeymapService extends BaseService {
|
||||||
|
|
||||||
private keymap: Keymap;
|
private keymap: Keymap;
|
||||||
private platform: string;
|
private platform: string;
|
||||||
private customKeymapPath: string;
|
private customKeymapPath: string;
|
||||||
private defaultKeymapItems: KeymapItem[];
|
private defaultKeymapItems: KeymapItem[];
|
||||||
private lastSaveTime_:number;
|
private lastSaveTime_:number;
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.lastSaveTime_ = Date.now();
|
this.lastSaveTime_ = Date.now();
|
||||||
@ -106,11 +109,11 @@ export default class KeymapService extends BaseService {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastSaveTime():number {
|
public get lastSaveTime():number {
|
||||||
return this.lastSaveTime_;
|
return this.lastSaveTime_;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(platform: string = shim.platformName()) {
|
public initialize(platform: string = shim.platformName()) {
|
||||||
this.platform = platform;
|
this.platform = platform;
|
||||||
|
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
@ -131,7 +134,7 @@ export default class KeymapService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCustomKeymap(customKeymapPath: string) {
|
public async loadCustomKeymap(customKeymapPath: string) {
|
||||||
this.customKeymapPath = customKeymapPath; // Useful for saving the changes later
|
this.customKeymapPath = customKeymapPath; // Useful for saving the changes later
|
||||||
|
|
||||||
if (await shim.fsDriver().exists(customKeymapPath)) {
|
if (await shim.fsDriver().exists(customKeymapPath)) {
|
||||||
@ -143,7 +146,7 @@ export default class KeymapService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveCustomKeymap(customKeymapPath: string = this.customKeymapPath) {
|
public async saveCustomKeymap(customKeymapPath: string = this.customKeymapPath) {
|
||||||
this.logger().info(`KeymapService: Saving keymap to file: ${customKeymapPath}`);
|
this.logger().info(`KeymapService: Saving keymap to file: ${customKeymapPath}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -161,7 +164,7 @@ export default class KeymapService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acceleratorExists(command: string) {
|
public acceleratorExists(command: string) {
|
||||||
return !!this.keymap[command];
|
return !!this.keymap[command];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,33 +192,33 @@ export default class KeymapService extends BaseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setAccelerator(command: string, accelerator: string) {
|
public setAccelerator(command: string, accelerator: string) {
|
||||||
this.keymap[command].accelerator = accelerator;
|
this.keymap[command].accelerator = accelerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccelerator(command: string) {
|
public getAccelerator(command: string) {
|
||||||
const item = this.keymap[command];
|
const item = this.keymap[command];
|
||||||
if (!item) throw new Error(`KeymapService: "${command}" command does not exist!`);
|
if (!item) throw new Error(`KeymapService: "${command}" command does not exist!`);
|
||||||
|
|
||||||
return item.accelerator;
|
return item.accelerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultAccelerator(command: string) {
|
public getDefaultAccelerator(command: string) {
|
||||||
const defaultItem = this.defaultKeymapItems.find((item => item.command === command));
|
const defaultItem = this.defaultKeymapItems.find((item => item.command === command));
|
||||||
if (!defaultItem) throw new Error(`KeymapService: "${command}" command does not exist!`);
|
if (!defaultItem) throw new Error(`KeymapService: "${command}" command does not exist!`);
|
||||||
|
|
||||||
return defaultItem.accelerator;
|
return defaultItem.accelerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCommandNames() {
|
public getCommandNames() {
|
||||||
return Object.keys(this.keymap);
|
return Object.keys(this.keymap);
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeymapItems() {
|
public getKeymapItems() {
|
||||||
return Object.values(this.keymap);
|
return Object.values(this.keymap);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCustomKeymapItems() {
|
public getCustomKeymapItems() {
|
||||||
const customkeymapItems: KeymapItem[] = [];
|
const customkeymapItems: KeymapItem[] = [];
|
||||||
this.defaultKeymapItems.forEach(({ command, accelerator }) => {
|
this.defaultKeymapItems.forEach(({ command, accelerator }) => {
|
||||||
const currentAccelerator = this.getAccelerator(command);
|
const currentAccelerator = this.getAccelerator(command);
|
||||||
@ -236,11 +239,11 @@ export default class KeymapService extends BaseService {
|
|||||||
return customkeymapItems;
|
return customkeymapItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultKeymapItems() {
|
public getDefaultKeymapItems() {
|
||||||
return [...this.defaultKeymapItems];
|
return [...this.defaultKeymapItems];
|
||||||
}
|
}
|
||||||
|
|
||||||
overrideKeymap(customKeymapItems: KeymapItem[]) {
|
public overrideKeymap(customKeymapItems: KeymapItem[]) {
|
||||||
try {
|
try {
|
||||||
for (let i = 0; i < customKeymapItems.length; i++) {
|
for (let i = 0; i < customKeymapItems.length; i++) {
|
||||||
const item = customKeymapItems[i];
|
const item = customKeymapItems[i];
|
||||||
@ -284,7 +287,7 @@ export default class KeymapService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateKeymap(proposedKeymapItem: KeymapItem = null) {
|
public validateKeymap(proposedKeymapItem: KeymapItem = null) {
|
||||||
const usedAccelerators = new Set();
|
const usedAccelerators = new Set();
|
||||||
|
|
||||||
// Validate as if the proposed change is already present in the current keymap
|
// Validate as if the proposed change is already present in the current keymap
|
||||||
@ -312,7 +315,7 @@ export default class KeymapService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAccelerator(accelerator: string) {
|
public validateAccelerator(accelerator: string) {
|
||||||
let keyFound = false;
|
let keyFound = false;
|
||||||
|
|
||||||
const parts = accelerator.split('+');
|
const parts = accelerator.split('+');
|
||||||
@ -334,7 +337,7 @@ export default class KeymapService extends BaseService {
|
|||||||
if (!isValid) throw new Error(_('Accelerator "%s" is not valid.', accelerator));
|
if (!isValid) throw new Error(_('Accelerator "%s" is not valid.', accelerator));
|
||||||
}
|
}
|
||||||
|
|
||||||
domToElectronAccelerator(event: KeyboardEvent<HTMLDivElement>) {
|
public domToElectronAccelerator(event: KeyboardEvent<HTMLDivElement>) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
const { key, ctrlKey, metaKey, altKey, shiftKey } = event;
|
const { key, ctrlKey, metaKey, altKey, shiftKey } = event;
|
||||||
|
|
||||||
@ -358,7 +361,7 @@ export default class KeymapService extends BaseService {
|
|||||||
return parts.join('+');
|
return parts.join('+');
|
||||||
}
|
}
|
||||||
|
|
||||||
static domToElectronKey(domKey: string) {
|
private static domToElectronKey(domKey: string) {
|
||||||
let electronKey;
|
let electronKey;
|
||||||
|
|
||||||
if (/^([a-z])$/.test(domKey)) {
|
if (/^([a-z])$/.test(domKey)) {
|
||||||
@ -398,7 +401,7 @@ export default class KeymapService extends BaseService {
|
|||||||
|
|
||||||
private static instance_:KeymapService = null;
|
private static instance_:KeymapService = null;
|
||||||
|
|
||||||
static instance():KeymapService {
|
public static instance():KeymapService {
|
||||||
if (this.instance_) return this.instance_;
|
if (this.instance_) return this.instance_;
|
||||||
|
|
||||||
this.instance_ = new KeymapService();
|
this.instance_ = new KeymapService();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
const Logger = require('lib/Logger').default;
|
const Logger = require('lib/Logger').default;
|
||||||
const KeymapService = require('lib/services/KeymapService').default;
|
|
||||||
|
|
||||||
class PluginManager {
|
class PluginManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -52,6 +51,7 @@ class PluginManager {
|
|||||||
const p = this.pluginInstance_(event.pluginName);
|
const p = this.pluginInstance_(event.pluginName);
|
||||||
p.onTrigger({
|
p.onTrigger({
|
||||||
itemName: event.itemName,
|
itemName: event.itemName,
|
||||||
|
userData: event.userData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class PluginManager {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
Dialog: Class.Dialog,
|
Dialog: Class.Dialog,
|
||||||
props: this.dialogProps_(name),
|
props: Object.assign({}, this.dialogProps_(name), { userData: p.userData }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,20 +81,24 @@ class PluginManager {
|
|||||||
|
|
||||||
menuItems() {
|
menuItems() {
|
||||||
let output = [];
|
let output = [];
|
||||||
const keymapService = KeymapService.instance();
|
|
||||||
|
|
||||||
for (const name in this.plugins_) {
|
for (const name in this.plugins_) {
|
||||||
const menuItems = this.plugins_[name].Class.manifest.menuItems;
|
const menuItems = this.plugins_[name].Class.manifest.menuItems.slice();
|
||||||
if (!menuItems) continue;
|
if (!menuItems) continue;
|
||||||
|
|
||||||
for (const item of menuItems) {
|
for (let i = 0; i < menuItems.length; i++) {
|
||||||
|
const item = Object.assign({}, menuItems[i]);
|
||||||
|
|
||||||
item.click = () => {
|
item.click = () => {
|
||||||
this.onPluginMenuItemTrigger_({
|
this.onPluginMenuItemTrigger_({
|
||||||
pluginName: name,
|
pluginName: name,
|
||||||
itemName: item.name,
|
itemName: item.name,
|
||||||
|
userData: item.userData,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
item.accelerator = keymapService.getAccelerator(name);
|
|
||||||
|
item.accelerator = menuItems[i].accelerator();
|
||||||
|
|
||||||
|
menuItems[i] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
output = output.concat(menuItems);
|
output = output.concat(menuItems);
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { ContextKeyExpr, ContextKeyExpression } from './contextkey/contextkey';
|
import { ContextKeyExpr, ContextKeyExpression } from './contextkey/contextkey';
|
||||||
|
|
||||||
export default class BooleanExpression {
|
export default class WhenClause {
|
||||||
|
|
||||||
private expression_:string;
|
private expression_:string;
|
||||||
|
private validate_:boolean;
|
||||||
private rules_:ContextKeyExpression = null;
|
private rules_:ContextKeyExpression = null;
|
||||||
|
|
||||||
constructor(expression:string) {
|
constructor(expression:string, validate:boolean) {
|
||||||
this.expression_ = expression;
|
this.expression_ = expression;
|
||||||
|
this.validate_ = validate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createContext(ctx: any) {
|
private createContext(ctx: any) {
|
||||||
@ -21,11 +23,20 @@ export default class BooleanExpression {
|
|||||||
if (!this.rules_) {
|
if (!this.rules_) {
|
||||||
this.rules_ = ContextKeyExpr.deserialize(this.expression_);
|
this.rules_ = ContextKeyExpr.deserialize(this.expression_);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.rules_;
|
return this.rules_;
|
||||||
}
|
}
|
||||||
|
|
||||||
public evaluate(context:any):boolean {
|
public evaluate(context:any):boolean {
|
||||||
|
if (this.validate_) this.validate(context);
|
||||||
return this.rules.evaluate(this.createContext(context));
|
return this.rules.evaluate(this.createContext(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public validate(context:any) {
|
||||||
|
const keys = this.rules.keys();
|
||||||
|
for (const key of keys) {
|
||||||
|
if (!(key in context)) throw new Error(`No such key: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -87,9 +87,9 @@ export default class MenuUtils {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public commandToStatefulMenuItem(commandName:string, props:any = null):MenuItem {
|
public commandToStatefulMenuItem(commandName:string, ...args:any[]):MenuItem {
|
||||||
return this.commandToMenuItem(commandName, () => {
|
return this.commandToMenuItem(commandName, () => {
|
||||||
return this.service.execute(commandName, props ? props : {});
|
return this.service.execute(commandName, ...args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,11 +108,14 @@ export default class MenuUtils {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public commandsToMenuItemProps(state:any, commandNames:string[]):MenuItemProps {
|
public commandsToMenuItemProps(commandNames:string[], whenClauseContext:any):MenuItemProps {
|
||||||
const output:MenuItemProps = {};
|
const output:MenuItemProps = {};
|
||||||
|
|
||||||
for (const commandName of commandNames) {
|
for (const commandName of commandNames) {
|
||||||
const newProps = this.service.commandMapStateToProps(commandName, state);
|
const newProps = {
|
||||||
|
enabled: this.service.isEnabled(commandName, whenClauseContext),
|
||||||
|
};
|
||||||
|
|
||||||
if (newProps === null || propsHaveChanged(this.menuItemPropsCache_[commandName], newProps)) {
|
if (newProps === null || propsHaveChanged(this.menuItemPropsCache_[commandName], newProps)) {
|
||||||
output[commandName] = newProps;
|
output[commandName] = newProps;
|
||||||
this.menuItemPropsCache_[commandName] = newProps;
|
this.menuItemPropsCache_[commandName] = newProps;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import CommandService from '../CommandService';
|
import CommandService from 'lib/services/CommandService';
|
||||||
import propsHaveChanged from './propsHaveChanged';
|
|
||||||
import { stateUtils } from 'lib/reducer';
|
import { stateUtils } from 'lib/reducer';
|
||||||
|
|
||||||
const separatorItem = { type: 'separator' };
|
const separatorItem = { type: 'separator' };
|
||||||
@ -14,7 +13,6 @@ export interface ToolbarButtonInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ToolbarButtonCacheItem {
|
interface ToolbarButtonCacheItem {
|
||||||
props: any,
|
|
||||||
info: ToolbarButtonInfo,
|
info: ToolbarButtonInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,8 +33,15 @@ export default class ToolbarButtonUtils {
|
|||||||
return this.service_;
|
return this.service_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private commandToToolbarButton(commandName:string, props:any):ToolbarButtonInfo {
|
private commandToToolbarButton(commandName:string, whenClauseContext:any):ToolbarButtonInfo {
|
||||||
if (this.toolbarButtonCache_[commandName] && !propsHaveChanged(this.toolbarButtonCache_[commandName].props, props)) {
|
const newEnabled = this.service.isEnabled(commandName, whenClauseContext);
|
||||||
|
const newTitle = this.service.title(commandName);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.toolbarButtonCache_[commandName] &&
|
||||||
|
this.toolbarButtonCache_[commandName].info.enabled === newEnabled &&
|
||||||
|
this.toolbarButtonCache_[commandName].info.title === newTitle
|
||||||
|
) {
|
||||||
return this.toolbarButtonCache_[commandName].info;
|
return this.toolbarButtonCache_[commandName].info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,15 +51,14 @@ export default class ToolbarButtonUtils {
|
|||||||
name: commandName,
|
name: commandName,
|
||||||
tooltip: this.service.label(commandName),
|
tooltip: this.service.label(commandName),
|
||||||
iconName: command.declaration.iconName,
|
iconName: command.declaration.iconName,
|
||||||
enabled: this.service.isEnabled(commandName, props),
|
enabled: newEnabled,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
this.service.execute(commandName, props);
|
this.service.execute(commandName);
|
||||||
},
|
},
|
||||||
title: this.service.title(commandName, props),
|
title: newTitle,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.toolbarButtonCache_[commandName] = {
|
this.toolbarButtonCache_[commandName] = {
|
||||||
props: props,
|
|
||||||
info: output,
|
info: output,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ export default class ToolbarButtonUtils {
|
|||||||
// This method ensures that if the provided commandNames and state hasn't changed
|
// This method ensures that if the provided commandNames and state hasn't changed
|
||||||
// the output also won't change. Invididual toolbarButtonInfo also won't changed
|
// the output also won't change. Invididual toolbarButtonInfo also won't changed
|
||||||
// if the state they use hasn't changed. This is to avoid useless renders of the toolbars.
|
// if the state they use hasn't changed. This is to avoid useless renders of the toolbars.
|
||||||
public commandsToToolbarButtons(state:any, commandNames:string[]):ToolbarButtonInfo[] {
|
public commandsToToolbarButtons(commandNames:string[], whenClauseContext:any):ToolbarButtonInfo[] {
|
||||||
const output:ToolbarButtonInfo[] = [];
|
const output:ToolbarButtonInfo[] = [];
|
||||||
|
|
||||||
for (const commandName of commandNames) {
|
for (const commandName of commandNames) {
|
||||||
@ -73,8 +77,7 @@ export default class ToolbarButtonUtils {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = this.service.commandMapStateToProps(commandName, state);
|
output.push(this.commandToToolbarButton(commandName, whenClauseContext));
|
||||||
output.push(this.commandToToolbarButton(commandName, props));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return stateUtils.selectArrayShallow({ array: output }, commandNames.join('_'));
|
return stateUtils.selectArrayShallow({ array: output }, commandNames.join('_'));
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import markdownUtils, { MarkdownTableHeader, MarkdownTableRow } from 'lib/markdownUtils';
|
||||||
|
|
||||||
|
export default function commandsToMarkdownTable():string {
|
||||||
|
const headers:MarkdownTableHeader[] = [
|
||||||
|
{
|
||||||
|
name: 'commandName',
|
||||||
|
label: 'Name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: 'Description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'props',
|
||||||
|
label: 'Props',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rows:MarkdownTableRow[] = [];
|
||||||
|
|
||||||
|
for (const commandName in this.commands_) {
|
||||||
|
|
||||||
|
const row:MarkdownTableRow = {
|
||||||
|
commandName: commandName,
|
||||||
|
description: this.label(commandName),
|
||||||
|
};
|
||||||
|
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdownUtils.createMarkdownTable(headers, rows);
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
export default function propsHaveChanged(previous:any, next:any):boolean {
|
export default function propsHaveChanged(previous:any, next:any):boolean {
|
||||||
if (!previous && next) return true;
|
if (!previous && next) return true;
|
||||||
|
if (previous && !next) return true;
|
||||||
|
if (!previous && !next) return false;
|
||||||
|
|
||||||
if (Object.keys(previous).length !== Object.keys(next).length) return true;
|
if (Object.keys(previous).length !== Object.keys(next).length) return true;
|
||||||
|
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
import { stateUtils } from 'lib/reducer';
|
||||||
|
|
||||||
|
const BaseModel = require('lib/BaseModel');
|
||||||
|
const Folder = require('lib/models/Folder');
|
||||||
|
const MarkupToHtml = require('lib/joplin-renderer/MarkupToHtml');
|
||||||
|
|
||||||
|
export default function stateToWhenClauseContext(state:any) {
|
||||||
|
const noteId = state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null;
|
||||||
|
const note = noteId ? BaseModel.byId(state.notes, noteId) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// UI elements
|
||||||
|
markdownEditorVisible: !!state.settings['editor.codeView'],
|
||||||
|
richTextEditorVisible: !state.settings['editor.codeView'],
|
||||||
|
markdownEditorPaneVisible: state.settings['editor.codeView'] && state.noteVisiblePanes.includes('editor'),
|
||||||
|
markdownViewerPaneVisible: state.settings['editor.codeView'] && state.noteVisiblePanes.includes('viewer'),
|
||||||
|
modalDialogVisible: !!Object.keys(state.visibleDialogs).length,
|
||||||
|
sideBarVisible: !!state.sidebarVisibility,
|
||||||
|
noteListHasNotes: !!state.notes.length,
|
||||||
|
|
||||||
|
// Application state
|
||||||
|
notesAreBeingSaved: stateUtils.hasNotesBeingSaved(state),
|
||||||
|
syncStarted: state.syncStarted,
|
||||||
|
|
||||||
|
// Current location
|
||||||
|
inConflictFolder: state.selectedFolderId === Folder.conflictFolderId(),
|
||||||
|
|
||||||
|
// Note selection
|
||||||
|
oneNoteSelected: !!note,
|
||||||
|
someNotesSelected: state.selectedNoteIds.length > 0,
|
||||||
|
multipleNotesSelected: state.selectedNoteIds.length > 1,
|
||||||
|
noNotesSelected: !state.selectedNoteIds.length,
|
||||||
|
|
||||||
|
// Note history
|
||||||
|
historyhasBackwardNotes: state.backwardHistoryNotes.length > 0,
|
||||||
|
historyhasForwardNotes: state.forwardHistoryNotes.length > 0,
|
||||||
|
|
||||||
|
// Folder selection
|
||||||
|
oneFolderSelected: !!state.selectedFolderId,
|
||||||
|
|
||||||
|
// Current note properties
|
||||||
|
noteIsTodo: note ? !!note.is_todo : false,
|
||||||
|
noteTodoCompleted: note ? !!note.todo_completed : false,
|
||||||
|
noteIsMarkdown: note ? note.markup_language === MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN : false,
|
||||||
|
noteIsHtml: note ? note.markup_language === MarkupToHtml.MARKUP_LANGUAGE_HTML : false,
|
||||||
|
};
|
||||||
|
}
|
@ -30,11 +30,11 @@ export default class JoplinCommands {
|
|||||||
*
|
*
|
||||||
* // Create a new sub-notebook under the provided notebook
|
* // Create a new sub-notebook under the provided notebook
|
||||||
* // Note: internally, notebooks are called "folders".
|
* // Note: internally, notebooks are called "folders".
|
||||||
* await joplin.commands.execute('newFolder', { parent_id: "SOME_FOLDER_ID" });
|
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async execute(commandName: string, props: any = null):Promise<any> {
|
async execute(commandName: string, ...args:any[]):Promise<any> {
|
||||||
return CommandService.instance().execute(commandName, props);
|
return CommandService.instance().execute(commandName, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,8 +65,7 @@ export default class JoplinCommands {
|
|||||||
execute: command.execute,
|
execute: command.execute,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ('isEnabled' in command) runtime.isEnabled = command.isEnabled;
|
if ('enabledCondition' in command) runtime.enabledCondition = command.enabledCondition;
|
||||||
if ('mapStateToProps' in command) runtime.mapStateToProps = command.mapStateToProps;
|
|
||||||
|
|
||||||
CommandService.instance().registerDeclaration(declaration);
|
CommandService.instance().registerDeclaration(declaration);
|
||||||
CommandService.instance().registerRuntime(declaration.name, runtime);
|
CommandService.instance().registerRuntime(declaration.name, runtime);
|
||||||
|
@ -3,12 +3,47 @@
|
|||||||
// =================================================================
|
// =================================================================
|
||||||
|
|
||||||
export interface Command {
|
export interface Command {
|
||||||
|
/**
|
||||||
|
* Name of command - must be globally unique
|
||||||
|
*/
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label to be displayed on menu items or keyboard shortcut editor for example
|
||||||
|
*/
|
||||||
label: string
|
label: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon to be used on toolbar buttons for example
|
||||||
|
*/
|
||||||
iconName?: string,
|
iconName?: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code to be ran when the command is executed. It maybe return a result.
|
||||||
|
*/
|
||||||
execute(props:any):Promise<any>
|
execute(props:any):Promise<any>
|
||||||
isEnabled?(props:any):boolean
|
|
||||||
mapStateToProps?(state:any):any
|
/**
|
||||||
|
* Defines whether the command should be enabled or disabled, which in turns affects
|
||||||
|
* the enabled state of any associated button or menu item.
|
||||||
|
*
|
||||||
|
* The condition should be expressed as a "when-clause" (as in Visual Studio Code). It's a simple boolean expression that evaluates to
|
||||||
|
* `true` or `false`. It supports the following operators:
|
||||||
|
*
|
||||||
|
* Operator | Symbol | Example
|
||||||
|
* -- | -- | --
|
||||||
|
* Equality | == | "editorType == markdown"
|
||||||
|
* Inequality | != | "currentScreen != config"
|
||||||
|
* Or | \|\| | "noteIsTodo \|\| noteTodoCompleted"
|
||||||
|
* And | && | "oneNoteSelected && !inConflictFolder"
|
||||||
|
*
|
||||||
|
* Currently the supported context variables aren't documented, but you can find the list there:
|
||||||
|
*
|
||||||
|
* https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/services/commands/stateToWhenClauseContext.ts
|
||||||
|
*
|
||||||
|
* Note: Commands are enabled by default unless you use this property.
|
||||||
|
*/
|
||||||
|
enabledCondition?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
|
@ -87,7 +87,7 @@ function filterLogs(logs, platform) {
|
|||||||
if (platform === 'android' && prefix.indexOf('android') >= 0) addIt = true;
|
if (platform === 'android' && prefix.indexOf('android') >= 0) addIt = true;
|
||||||
if (platform === 'ios' && prefix.indexOf('ios') >= 0) addIt = true;
|
if (platform === 'ios' && prefix.indexOf('ios') >= 0) addIt = true;
|
||||||
if (platform === 'desktop' && prefix.indexOf('desktop') >= 0) addIt = true;
|
if (platform === 'desktop' && prefix.indexOf('desktop') >= 0) addIt = true;
|
||||||
if (platform === 'desktop' && (prefix.indexOf('desktop') >= 0 || prefix.indexOf('api') >= 0)) addIt = true;
|
if (platform === 'desktop' && (prefix.indexOf('desktop') >= 0 || prefix.indexOf('api') >= 0 || prefix.indexOf('plugins') >= 0)) addIt = true;
|
||||||
if (platform === 'cli' && prefix.indexOf('cli') >= 0) addIt = true;
|
if (platform === 'cli' && prefix.indexOf('cli') >= 0) addIt = true;
|
||||||
if (platform === 'clipper' && prefix.indexOf('clipper') >= 0) addIt = true;
|
if (platform === 'clipper' && prefix.indexOf('clipper') >= 0) addIt = true;
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ function formatCommitMessage(msg, author, options) {
|
|||||||
const isPlatformPrefix = prefix => {
|
const isPlatformPrefix = prefix => {
|
||||||
prefix = prefix.split(',').map(p => p.trim().toLowerCase());
|
prefix = prefix.split(',').map(p => p.trim().toLowerCase());
|
||||||
for (const p of prefix) {
|
for (const p of prefix) {
|
||||||
if (['android', 'mobile', 'ios', 'desktop', 'cli', 'clipper', 'all', 'api'].indexOf(p) >= 0) return true;
|
if (['android', 'mobile', 'ios', 'desktop', 'cli', 'clipper', 'all', 'api', 'plugins'].indexOf(p) >= 0) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@ -129,6 +129,7 @@ function formatCommitMessage(msg, author, options) {
|
|||||||
if (splitted.length) {
|
if (splitted.length) {
|
||||||
const platform = splitted[0].trim().toLowerCase();
|
const platform = splitted[0].trim().toLowerCase();
|
||||||
if (platform === 'api') subModule = 'api';
|
if (platform === 'api') subModule = 'api';
|
||||||
|
if (platform === 'plugins') subModule = 'plugins';
|
||||||
if (isPlatformPrefix(platform)) {
|
if (isPlatformPrefix(platform)) {
|
||||||
splitted.splice(0, 1);
|
splitted.splice(0, 1);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ module.exports = {
|
|||||||
'**/Modules/TinyMCE/IconPack/**',
|
'**/Modules/TinyMCE/IconPack/**',
|
||||||
'**/CliClient/tests/support/plugins/**',
|
'**/CliClient/tests/support/plugins/**',
|
||||||
'**/plugin_types/**',
|
'**/plugin_types/**',
|
||||||
|
'**/ReactNativeClient/android/**',
|
||||||
|
'**/ReactNativeClient/ios/**',
|
||||||
],
|
],
|
||||||
}).map(f => f.substr(rootDir.length + 1));
|
}).map(f => f.substr(rootDir.length + 1));
|
||||||
|
|
||||||
|
@ -59,7 +59,6 @@
|
|||||||
"ElectronClient/dist/": true,
|
"ElectronClient/dist/": true,
|
||||||
"ElectronClient/fonts/": true,
|
"ElectronClient/fonts/": true,
|
||||||
"ElectronClient/gui/note-viewer/highlight/styles/": true,
|
"ElectronClient/gui/note-viewer/highlight/styles/": true,
|
||||||
"ElectronClient/lib/": true,
|
|
||||||
"ElectronClient/locale/": true,
|
"ElectronClient/locale/": true,
|
||||||
"ElectronClient/build/": true,
|
"ElectronClient/build/": true,
|
||||||
"node_modules/": true,
|
"node_modules/": true,
|
||||||
@ -255,6 +254,7 @@
|
|||||||
"ElectronClient/**/.DS_Store": true,
|
"ElectronClient/**/.DS_Store": true,
|
||||||
"ElectronClient/**/gui/note-viewer/pluginAssets/": true,
|
"ElectronClient/**/gui/note-viewer/pluginAssets/": true,
|
||||||
"ElectronClient/**/pluginAssets/": true,
|
"ElectronClient/**/pluginAssets/": true,
|
||||||
|
"ElectronClient/lib/": true,
|
||||||
"Clipper/popup/**/node_modules": true,
|
"Clipper/popup/**/node_modules": true,
|
||||||
"Clipper/popup/**/coverage": true,
|
"Clipper/popup/**/coverage": true,
|
||||||
"Clipper/popup/**/build": true,
|
"Clipper/popup/**/build": true,
|
||||||
@ -360,7 +360,9 @@
|
|||||||
"CliClient/tests/support/plugins/settings/**/node_modules/": true,
|
"CliClient/tests/support/plugins/settings/**/node_modules/": true,
|
||||||
"CliClient/tests/support/plugins/selected_text/dist/*": true,
|
"CliClient/tests/support/plugins/selected_text/dist/*": true,
|
||||||
"CliClient/tests/support/plugins/selected_text/**/node_modules/": true,
|
"CliClient/tests/support/plugins/selected_text/**/node_modules/": true,
|
||||||
"**/CliClient/build/": true
|
"**/CliClient/build/": true,
|
||||||
|
"**/*.js": {"when": "$(basename).ts"},
|
||||||
|
"**/*?.js": { "when": "$(basename).tsx"},
|
||||||
},
|
},
|
||||||
"spellright.language": [
|
"spellright.language": [
|
||||||
"en"
|
"en"
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"generatePluginTypes": "rm -rf ./plugin_types && node node_modules/typescript/bin/tsc --declaration --declarationDir ./plugin_types --project tsconfig.json",
|
"generatePluginTypes": "rm -rf ./plugin_types && node node_modules/typescript/bin/tsc --declaration --declarationDir ./plugin_types --project tsconfig.json",
|
||||||
"setupNewRelease": "node ./Tools/setupNewRelease",
|
"setupNewRelease": "node ./Tools/setupNewRelease",
|
||||||
"linkChecker": "linkchecker https://joplinapp.org",
|
"linkChecker": "linkchecker https://joplinapp.org",
|
||||||
"clean": "npm run clean",
|
"clean": "gulp clean",
|
||||||
"postinstall": "cd Tools && npm i && cd .. && cd ReactNativeClient && npm i && cd .. && cd ElectronClient && npm i && cd .. && cd CliClient && npm i && cd .. && gulp build"
|
"postinstall": "cd Tools && npm i && cd .. && cd ReactNativeClient && npm i && cd .. && cd ElectronClient && npm i && cd .. && cd CliClient && npm i && cd .. && gulp build"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
|
@ -125,7 +125,7 @@ joplin.plugins.register({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Later you will also need a way to generate the slug for each header. A slug is an identifier which is used to link to a particular header. Essentially a header text like "My Header" is converted to "my-header". And if there's already a slug with that name, a number is appended to it. Without going into too much details, you will need the "slug" package to generate this for you, so install it using `npm i -s uslug` from the root of your plugin directory.
|
Later you will also need a way to generate the slug for each header. A slug is an identifier which is used to link to a particular header. Essentially a header text like "My Header" is converted to "my-header". And if there's already a slug with that name, a number is appended to it. Without going into too much details, you will need the "slug" package to generate this for you, so install it using `npm i -s 'git+https://github.com/laurent22/uslug.git#emoji-support'` from the root of your plugin directory (Note: you can also install the "uslug" package on its own, but it won't have emoji support).
|
||||||
|
|
||||||
Then this is the function you will need for Joplin, so copy it somewhere in your file:
|
Then this is the function you will need for Joplin, so copy it somewhere in your file:
|
||||||
|
|
||||||
@ -322,9 +322,7 @@ joplin.plugins.register({
|
|||||||
if (message.name === 'scrollToHash') {
|
if (message.name === 'scrollToHash') {
|
||||||
// As the name says, the scrollToHash command makes the note scroll
|
// As the name says, the scrollToHash command makes the note scroll
|
||||||
// to the provided hash.
|
// to the provided hash.
|
||||||
joplin.commands.execute('scrollToHash', {
|
joplin.commands.execute('scrollToHash', message.hash)
|
||||||
hash: message.hash,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user