mirror of https://github.com/laurent22/joplin.git
Merge branch 'dev' into plugin_editor_context_menu
commit
46c40ce9fa
|
@ -433,6 +433,9 @@ packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js.map
|
|||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js.map
|
||||
|
@ -583,6 +586,9 @@ packages/app-desktop/gui/NoteListControls/commands/focusSearch.js.map
|
|||
packages/app-desktop/gui/NoteListItem.d.ts
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem.js.map
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.d.ts
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.js
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.js.map
|
||||
packages/app-desktop/gui/NoteTextViewer.d.ts
|
||||
packages/app-desktop/gui/NoteTextViewer.js
|
||||
packages/app-desktop/gui/NoteTextViewer.js.map
|
||||
|
@ -592,15 +598,60 @@ packages/app-desktop/gui/NoteToolbar/NoteToolbar.js.map
|
|||
packages/app-desktop/gui/OneDriveLoginScreen.d.ts
|
||||
packages/app-desktop/gui/OneDriveLoginScreen.js
|
||||
packages/app-desktop/gui/OneDriveLoginScreen.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/ResizableLayout.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/ResizableLayout.js
|
||||
packages/app-desktop/gui/ResizableLayout/ResizableLayout.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useLayoutItemSizes.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useLayoutItemSizes.js
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useLayoutItemSizes.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useWindowResizeEvent.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useWindowResizeEvent.js
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useWindowResizeEvent.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/isTempContainer.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/isTempContainer.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/isTempContainer.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/iterateItems.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/iterateItems.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/iterateItems.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/layoutItemProp.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/layoutItemProp.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/layoutItemProp.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.test.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.test.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.test.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.test.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/style.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/style.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/style.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/types.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/types.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/types.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useWindowResizeEvent.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useWindowResizeEvent.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useWindowResizeEvent.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/validateLayout.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/validateLayout.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/validateLayout.js.map
|
||||
packages/app-desktop/gui/ResourceScreen.d.ts
|
||||
packages/app-desktop/gui/ResourceScreen.js
|
||||
packages/app-desktop/gui/ResourceScreen.js.map
|
||||
|
@ -679,6 +730,9 @@ packages/app-desktop/plugins/GotoAnything.js.map
|
|||
packages/app-desktop/services/bridge.d.ts
|
||||
packages/app-desktop/services/bridge.js
|
||||
packages/app-desktop/services/bridge.js.map
|
||||
packages/app-desktop/services/commands/stateToWhenClauseContext.d.ts
|
||||
packages/app-desktop/services/commands/stateToWhenClauseContext.js
|
||||
packages/app-desktop/services/commands/stateToWhenClauseContext.js.map
|
||||
packages/app-desktop/services/commands/types.d.ts
|
||||
packages/app-desktop/services/commands/types.js
|
||||
packages/app-desktop/services/commands/types.js.map
|
||||
|
|
|
@ -25,6 +25,9 @@ module.exports = {
|
|||
'afterEach': 'readonly',
|
||||
'jasmine': 'readonly',
|
||||
|
||||
// Jest variables
|
||||
'test': 'readonly',
|
||||
|
||||
// React Native variables
|
||||
'__DEV__': 'readonly',
|
||||
|
||||
|
|
|
@ -425,6 +425,9 @@ packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js.map
|
|||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js.map
|
||||
|
@ -575,6 +578,9 @@ packages/app-desktop/gui/NoteListControls/commands/focusSearch.js.map
|
|||
packages/app-desktop/gui/NoteListItem.d.ts
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListItem.js.map
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.d.ts
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.js
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.js.map
|
||||
packages/app-desktop/gui/NoteTextViewer.d.ts
|
||||
packages/app-desktop/gui/NoteTextViewer.js
|
||||
packages/app-desktop/gui/NoteTextViewer.js.map
|
||||
|
@ -584,15 +590,60 @@ packages/app-desktop/gui/NoteToolbar/NoteToolbar.js.map
|
|||
packages/app-desktop/gui/OneDriveLoginScreen.d.ts
|
||||
packages/app-desktop/gui/OneDriveLoginScreen.js
|
||||
packages/app-desktop/gui/OneDriveLoginScreen.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/ResizableLayout.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/ResizableLayout.js
|
||||
packages/app-desktop/gui/ResizableLayout/ResizableLayout.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useLayoutItemSizes.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useLayoutItemSizes.js
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useLayoutItemSizes.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useWindowResizeEvent.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useWindowResizeEvent.js
|
||||
packages/app-desktop/gui/ResizableLayout/hooks/useWindowResizeEvent.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/isTempContainer.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/isTempContainer.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/isTempContainer.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/iterateItems.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/iterateItems.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/iterateItems.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/layoutItemProp.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/layoutItemProp.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/layoutItemProp.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.test.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.test.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/movements.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.test.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.test.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/persist.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/style.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/style.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/style.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/types.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/types.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/types.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useWindowResizeEvent.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useWindowResizeEvent.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/useWindowResizeEvent.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/validateLayout.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/validateLayout.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/validateLayout.js.map
|
||||
packages/app-desktop/gui/ResourceScreen.d.ts
|
||||
packages/app-desktop/gui/ResourceScreen.js
|
||||
packages/app-desktop/gui/ResourceScreen.js.map
|
||||
|
@ -671,6 +722,9 @@ packages/app-desktop/plugins/GotoAnything.js.map
|
|||
packages/app-desktop/services/bridge.d.ts
|
||||
packages/app-desktop/services/bridge.js
|
||||
packages/app-desktop/services/bridge.js.map
|
||||
packages/app-desktop/services/commands/stateToWhenClauseContext.d.ts
|
||||
packages/app-desktop/services/commands/stateToWhenClauseContext.js
|
||||
packages/app-desktop/services/commands/stateToWhenClauseContext.js.map
|
||||
packages/app-desktop/services/commands/types.d.ts
|
||||
packages/app-desktop/services/commands/types.js
|
||||
packages/app-desktop/services/commands/types.js.map
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"files.exclude": {
|
||||
"lerna-debug.log": true,
|
||||
"_mydocs/mdtest/": true,
|
||||
"./packages/lib/plugin_types": true,
|
||||
"_releases/": true,
|
||||
"_vieux/": true,
|
||||
".gitignore": true,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
|
||||
import ToolbarButtonUtils from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import CommandService, { CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||
import stateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
|
||||
import KeymapService from '@joplin/lib/services/KeymapService';
|
||||
|
||||
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow } = require('./test-utils.js');
|
||||
|
@ -17,7 +18,7 @@ function newService(): CommandService {
|
|||
return {};
|
||||
},
|
||||
};
|
||||
service.initialize(mockStore, true);
|
||||
service.initialize(mockStore, true, stateToWhenClauseContext);
|
||||
return service;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@ joplin.plugins.register({
|
|||
onStart: async function() {
|
||||
const dialogs = joplin.views.dialogs;
|
||||
|
||||
const handle = await dialogs.create();
|
||||
const handle = await dialogs.create('myDialog1');
|
||||
await dialogs.setHtml(handle, '<p>Testing dialog with default buttons</p><p>Second line</p><p>Third line</p>');
|
||||
const result = await dialogs.open(handle);
|
||||
alert('Got result: ' + JSON.stringify(result));
|
||||
|
||||
const handle2 = await dialogs.create();
|
||||
const handle2 = await dialogs.create('myDialog2');
|
||||
await dialogs.setHtml(handle2, '<p>Testing dialog with custom buttons</p><p>Second line</p><p>Third line</p>');
|
||||
await dialogs.setButtons(handle2, [
|
||||
{
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Plugin from '../Plugin';
|
||||
import Joplin from './Joplin';
|
||||
import Logger from 'lib/Logger';
|
||||
import Logger from '../../../Logger';
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
|
|
@ -7,7 +7,7 @@ import JoplinCommands from './JoplinCommands';
|
|||
import JoplinViews from './JoplinViews';
|
||||
import JoplinInterop from './JoplinInterop';
|
||||
import JoplinSettings from './JoplinSettings';
|
||||
import Logger from 'lib/Logger';
|
||||
import Logger from '../../../Logger';
|
||||
/**
|
||||
* This is the main entry point to the Joplin API. You can access various services using the provided accessors.
|
||||
*/
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
import { Command } from './types';
|
||||
/**
|
||||
* This class allows executing or registering new Joplin commands. Commands can be executed or associated with
|
||||
* {@link JoplinViewsToolbarButtons | toolbar buttons} or {@link JoplinViewsMenuItems | menu items}.
|
||||
* This class allows executing or registering new Joplin commands. Commands
|
||||
* can be executed or associated with
|
||||
* {@link JoplinViewsToolbarButtons | toolbar buttons} or
|
||||
* {@link JoplinViewsMenuItems | menu items}.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/register_command)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
|
||||
*
|
||||
* ## Executing Joplin's internal commands
|
||||
*
|
||||
* It is also possible to execute internal Joplin's commands which, as of now, are not well documented.
|
||||
* You can find the list directly on GitHub though at the following locations:
|
||||
* It is also possible to execute internal Joplin's commands which, as of
|
||||
* now, are not well documented. You can find the list directly on GitHub
|
||||
* though at the following locations:
|
||||
*
|
||||
* https://github.com/laurent22/joplin/tree/dev/ElectronClient/gui/MainScreen/commands
|
||||
* https://github.com/laurent22/joplin/tree/dev/ElectronClient/commands
|
||||
* https://github.com/laurent22/joplin/tree/dev/ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.ts
|
||||
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
|
||||
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
|
||||
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.ts)
|
||||
*
|
||||
* To view what arguments are supported, you can open any of these files and look at the `execute()` command.
|
||||
* To view what arguments are supported, you can open any of these files
|
||||
* and look at the `execute()` command.
|
||||
*/
|
||||
export default class JoplinCommands {
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Executes the given command.
|
||||
* The `props` are the arguments passed to the command, and they vary based on the command
|
||||
* <span class="platform-desktop">desktop</span> Executes the given
|
||||
* command.
|
||||
*
|
||||
* The command can take any number of arguments, and the supported
|
||||
* arguments will vary based on the command. For custom commands, this
|
||||
* is the `args` passed to the `execute()` function. For built-in
|
||||
* commands, you can find the supported arguments by checking the links
|
||||
* above.
|
||||
*
|
||||
* ```typescript
|
||||
* // Create a new note in the current notebook:
|
||||
|
@ -27,10 +37,10 @@ export default class JoplinCommands {
|
|||
*
|
||||
* // Create a new sub-notebook under the provided notebook
|
||||
* // Note: internally, notebooks are called "folders".
|
||||
* await joplin.commands.execute('newFolder', { parent_id: "SOME_FOLDER_ID" });
|
||||
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
|
||||
* ```
|
||||
*/
|
||||
execute(commandName: string, props?: any): Promise<any>;
|
||||
execute(commandName: string, ...args: any[]): Promise<any | void>;
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Registers a new command.
|
||||
*
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Path } from './types';
|
|||
*
|
||||
* This is also what you would use to search notes, via the `search` endpoint.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/simple)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/simple)
|
||||
*
|
||||
* In general you would use the methods in this class as if you were using a REST API. There are four methods that map to GET, POST, PUT and DELETE calls.
|
||||
* And each method takes these parameters:
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ExportModule, ImportModule } from './types';
|
|||
/**
|
||||
* Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/json_export)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export)
|
||||
*
|
||||
* To implement an import or export module, you would simply define an object with various event handlers that are called
|
||||
* by the application during the import/export process.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Plugin from '../Plugin';
|
||||
import Logger from 'lib/Logger';
|
||||
import { Script } from './types';
|
||||
import Logger from '../../../Logger';
|
||||
import { ContentScriptType, Script } from './types';
|
||||
/**
|
||||
* This class provides access to plugin-related features.
|
||||
*/
|
||||
|
@ -21,4 +21,18 @@ export default class JoplinPlugins {
|
|||
* ```
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* Registers a new content script. Unlike regular plugin code, which runs in a separate process, content scripts run within the main process code
|
||||
* and thus allow improved performances and more customisations in specific cases. It can be used for example to load a Markdown or editor plugin.
|
||||
*
|
||||
* Note that registering a content script in itself will do nothing - it will only be loaded in specific cases by the relevant app modules
|
||||
* (eg. the Markdown renderer or the code editor). So it is not a way to inject and run arbitrary code in the app, which for safety and performance reasons is not supported.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||
*
|
||||
* @param type Defines how the script will be used. See the type definition for more information about each supported type.
|
||||
* @param id A unique ID for the content script.
|
||||
* @param scriptPath Must be a path relative to the plugin main script. For example, if your file content_script.js is next to your index.ts file, you would set `scriptPath` to `"./content_script.js`.
|
||||
*/
|
||||
registerContentScript(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { SettingItem, SettingSection } from './types';
|
|||
*
|
||||
* Note: Currently this API does **not** provide access to Joplin's built-in settings. This is by design as plugins that modify user settings could give unexpected results
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/settings)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/settings)
|
||||
*/
|
||||
export default class JoplinSettings {
|
||||
private plugin_;
|
||||
|
@ -37,7 +37,7 @@ export default class JoplinSettings {
|
|||
*
|
||||
* The list of available settings is not documented yet, but can be found by looking at the source code:
|
||||
*
|
||||
* https://github.com/laurent22/joplin/blob/3539a452a359162c461d2849829d2d42973eab50/ReactNativeClient/lib/models/Setting.ts#L142
|
||||
* https://github.com/laurent22/joplin/blob/3539a452a359162c461d2849829d2d42973eab50/packages/app-mobile/lib/models/Setting.ts#L142
|
||||
*/
|
||||
globalValue(key: string): Promise<any>;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ButtonSpec, ViewHandle, ButtonId } from './types';
|
|||
* Dialogs are hidden by default and you need to call `open()` to open them. Once the user clicks on a button, the `open` call will return and provide the button ID that was
|
||||
* clicked on. There is currently no "close" method since the dialog should be thought as a modal one and thus can only be closed by clicking on one of the buttons.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/dialog)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog)
|
||||
*/
|
||||
export default class JoplinViewsDialogs {
|
||||
private store;
|
||||
|
@ -16,7 +16,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@ import Plugin from '../Plugin';
|
|||
/**
|
||||
* Allows creating and managing menu items.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/register_command)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
|
||||
*/
|
||||
export default class JoplinViewsMenuItems {
|
||||
private store;
|
||||
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import Plugin from '../Plugin';
|
|||
/**
|
||||
* Allows creating menus.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/menu)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/menu)
|
||||
*/
|
||||
export default class JoplinViewsMenus {
|
||||
private store;
|
||||
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/toc)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
export default class JoplinViewsPanels {
|
||||
private store;
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@ import Plugin from '../Plugin';
|
|||
/**
|
||||
* Allows creating and managing toolbar buttons.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/register_command)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
|
||||
*/
|
||||
export default class JoplinViewsToolbarButtons {
|
||||
private store;
|
||||
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* The workspace service provides access to all the parts of Joplin that are being worked on - i.e. the currently selected notes or notebooks as well
|
||||
* as various related events, such as when a new note is selected, or when the note content changes.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins)
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins)
|
||||
*/
|
||||
export default class JoplinWorkspace {
|
||||
private store;
|
||||
|
|
|
@ -3,12 +3,48 @@
|
|||
// =================================================================
|
||||
|
||||
export interface Command {
|
||||
name: string
|
||||
label: string
|
||||
iconName?: string,
|
||||
execute(props:any):Promise<any>
|
||||
isEnabled?(props:any):boolean
|
||||
mapStateToProps?(state:any):any
|
||||
/**
|
||||
* Name of command - must be globally unique
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Label to be displayed on menu items or keyboard shortcut editor for example.
|
||||
* If it is missing, it's assumed it's a private command, to be called programmatically only.
|
||||
* In that case the command will not appear in the shortcut editor or command panel, and logically
|
||||
* should not be used as a menu item.
|
||||
*/
|
||||
label?: string;
|
||||
|
||||
/**
|
||||
* Icon to be used on toolbar buttons for example
|
||||
*/
|
||||
iconName?: string;
|
||||
|
||||
/**
|
||||
* Code to be ran when the command is executed. It may return a result.
|
||||
*/
|
||||
execute(...args: any[]): Promise<any | void>;
|
||||
|
||||
/**
|
||||
* 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 here](https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/lib/services/commands/stateToWhenClauseContext.ts).
|
||||
*
|
||||
* Note: Commands are enabled by default unless you use this property.
|
||||
*/
|
||||
enabledCondition?: string;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
@ -26,7 +62,7 @@ export enum ImportModuleOutputFormat {
|
|||
}
|
||||
|
||||
/**
|
||||
* Used to implement a module to export data from Joplin. [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/json_export) for an example.
|
||||
* Used to implement a module to export data from Joplin. [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export) for an example.
|
||||
*
|
||||
* In general, all the event handlers you'll need to implement take a `context` object as a first argument. This object will contain the export or import path as well as various optional properties, such as which notes or notebooks need to be exported.
|
||||
*
|
||||
|
@ -36,113 +72,113 @@ export interface ExportModule {
|
|||
/**
|
||||
* The format to be exported, eg "enex", "jex", "json", etc.
|
||||
*/
|
||||
format: string,
|
||||
format: string;
|
||||
|
||||
/**
|
||||
* The description that will appear in the UI, for example in the menu item.
|
||||
*/
|
||||
description: string,
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Whether the module will export a single file or multiple files in a directory. It affects the open dialog that will be presented to the user when using your exporter.
|
||||
*/
|
||||
target: FileSystemItem,
|
||||
target: FileSystemItem;
|
||||
|
||||
/**
|
||||
* Only applies to single file exporters or importers
|
||||
* It tells whether the format can package multiple notes into one file.
|
||||
* For example JEX or ENEX can, but HTML cannot.
|
||||
*/
|
||||
isNoteArchive: boolean,
|
||||
isNoteArchive: boolean;
|
||||
|
||||
/**
|
||||
* The extensions of the files exported by your module. For example, it is `["htm", "html"]` for the HTML module, and just `["jex"]` for the JEX module.
|
||||
*/
|
||||
fileExtensions?: string[],
|
||||
fileExtensions?: string[];
|
||||
|
||||
/**
|
||||
* Called when the export process starts.
|
||||
*/
|
||||
onInit(context:ExportContext): Promise<void>;
|
||||
onInit(context: ExportContext): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called when an item needs to be processed. An "item" can be any Joplin object, such as a note, a folder, a notebook, etc.
|
||||
*/
|
||||
onProcessItem(context:ExportContext, itemType:number, item:any):Promise<void>;
|
||||
onProcessItem(context: ExportContext, itemType: number, item: any): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called when a resource file needs to be exported.
|
||||
*/
|
||||
onProcessResource(context:ExportContext, resource:any, filePath:string):Promise<void>;
|
||||
onProcessResource(context: ExportContext, resource: any, filePath: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called when the export process is done.
|
||||
*/
|
||||
onClose(context:ExportContext):Promise<void>;
|
||||
onClose(context: ExportContext): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ImportModule {
|
||||
/**
|
||||
* The format to be exported, eg "enex", "jex", "json", etc.
|
||||
*/
|
||||
format: string,
|
||||
format: string;
|
||||
|
||||
/**
|
||||
* The description that will appear in the UI, for example in the menu item.
|
||||
*/
|
||||
description: string,
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Only applies to single file exporters or importers
|
||||
* It tells whether the format can package multiple notes into one file.
|
||||
* For example JEX or ENEX can, but HTML cannot.
|
||||
*/
|
||||
isNoteArchive: boolean,
|
||||
isNoteArchive: boolean;
|
||||
|
||||
/**
|
||||
* The type of sources that are supported by the module. Tells whether the module can import files or directories or both.
|
||||
*/
|
||||
sources: FileSystemItem[],
|
||||
sources: FileSystemItem[];
|
||||
|
||||
/**
|
||||
* Tells the file extensions of the exported files.
|
||||
*/
|
||||
fileExtensions?: string[],
|
||||
fileExtensions?: string[];
|
||||
|
||||
/**
|
||||
* Tells the type of notes that will be generated, either HTML or Markdown (default).
|
||||
*/
|
||||
outputFormat?: ImportModuleOutputFormat,
|
||||
outputFormat?: ImportModuleOutputFormat;
|
||||
|
||||
/**
|
||||
* Called when the import process starts. There is only one event handler within which you should import the complete data.
|
||||
*/
|
||||
onExec(context:ImportContext): Promise<void>;
|
||||
onExec(context: ImportContext): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExportOptions {
|
||||
format?: string,
|
||||
path?:string,
|
||||
sourceFolderIds?: string[],
|
||||
sourceNoteIds?: string[],
|
||||
modulePath?:string,
|
||||
target?:FileSystemItem,
|
||||
format?: string;
|
||||
path?: string;
|
||||
sourceFolderIds?: string[];
|
||||
sourceNoteIds?: string[];
|
||||
modulePath?: string;
|
||||
target?: FileSystemItem;
|
||||
}
|
||||
|
||||
export interface ExportContext {
|
||||
destPath: string,
|
||||
options: ExportOptions,
|
||||
destPath: string;
|
||||
options: ExportOptions;
|
||||
|
||||
/**
|
||||
* You can attach your own custom data using this propery - it will then be passed to each event handler, allowing you to keep state from one event to the next.
|
||||
*/
|
||||
userData?: any,
|
||||
userData?: any;
|
||||
}
|
||||
|
||||
export interface ImportContext {
|
||||
sourcePath: string,
|
||||
options: any,
|
||||
warnings: string[],
|
||||
sourcePath: string;
|
||||
options: any;
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
@ -150,7 +186,7 @@ export interface ImportContext {
|
|||
// =================================================================
|
||||
|
||||
export interface Script {
|
||||
onStart?(event:any):Promise<void>,
|
||||
onStart?(event: any): Promise<void>;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
@ -158,7 +194,7 @@ export interface Script {
|
|||
// =================================================================
|
||||
|
||||
export interface CreateMenuItemOptions {
|
||||
accelerator: string,
|
||||
accelerator: string;
|
||||
}
|
||||
|
||||
export enum MenuItemLocation {
|
||||
|
@ -172,10 +208,26 @@ export enum MenuItemLocation {
|
|||
}
|
||||
|
||||
export interface MenuItem {
|
||||
commandName?: string,
|
||||
accelerator?: string,
|
||||
submenu?: MenuItem[],
|
||||
label?: string,
|
||||
/**
|
||||
* Command that should be associated with the menu item. All menu item should
|
||||
* have a command associated with them unless they are a sub-menu.
|
||||
*/
|
||||
commandName?: string;
|
||||
|
||||
/**
|
||||
* Accelerator associated with the menu item
|
||||
*/
|
||||
accelerator?: string;
|
||||
|
||||
/**
|
||||
* Menu items that should appear below this menu item. Allows creating a menu tree.
|
||||
*/
|
||||
submenu?: MenuItem[];
|
||||
|
||||
/**
|
||||
* Menu item label. If not specified, the command label will be used instead.
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
@ -183,9 +235,9 @@ export interface MenuItem {
|
|||
// =================================================================
|
||||
|
||||
export interface ButtonSpec {
|
||||
id: ButtonId,
|
||||
title?: string,
|
||||
onClick?():void,
|
||||
id: ButtonId;
|
||||
title?: string;
|
||||
onClick?(): void;
|
||||
}
|
||||
|
||||
export type ButtonId = string;
|
||||
|
@ -225,28 +277,28 @@ export enum SettingItemType {
|
|||
// Redefine a simplified interface to mask internal details
|
||||
// and to remove function calls as they would have to be async.
|
||||
export interface SettingItem {
|
||||
value: any,
|
||||
type: SettingItemType,
|
||||
public: boolean,
|
||||
label:string,
|
||||
value: any;
|
||||
type: SettingItemType;
|
||||
public: boolean;
|
||||
label: string;
|
||||
|
||||
description?:string,
|
||||
isEnum?: boolean,
|
||||
section?: string,
|
||||
options?:any,
|
||||
appTypes?:string[],
|
||||
secure?: boolean,
|
||||
advanced?: boolean,
|
||||
minimum?: number,
|
||||
maximum?: number,
|
||||
step?: number,
|
||||
description?: string;
|
||||
isEnum?: boolean;
|
||||
section?: string;
|
||||
options?: any;
|
||||
appTypes?: string[];
|
||||
secure?: boolean;
|
||||
advanced?: boolean;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
export interface SettingSection {
|
||||
label: string,
|
||||
iconName?: string,
|
||||
description?: string,
|
||||
name?: string,
|
||||
label: string;
|
||||
iconName?: string;
|
||||
description?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
@ -261,3 +313,48 @@ export interface SettingSection {
|
|||
* [2]: (Optional) Resource link.
|
||||
*/
|
||||
export type Path = string[];
|
||||
|
||||
// =================================================================
|
||||
// Plugins type
|
||||
// =================================================================
|
||||
|
||||
export enum ContentScriptType {
|
||||
/**
|
||||
* Registers a new Markdown-It plugin, which should follow the template below.
|
||||
*
|
||||
* ```javascript
|
||||
* module.exports = {
|
||||
* default: function(context) {
|
||||
* return {
|
||||
* plugin: function(markdownIt, options) {
|
||||
* // ...
|
||||
* },
|
||||
* assets: {
|
||||
* // ...
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* - The `context` argument is currently unused but could be used later on to provide access to your own plugin so that the content script and plugin can communicate.
|
||||
*
|
||||
* - The **required** `plugin` key is the actual Markdown-It plugin - check the [official doc](https://github.com/markdown-it/markdown-it) for more information. The `options` parameter is of type [RuleOptions](https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/lib/joplin-renderer/MdToHtml.ts), which contains a number of options, mostly useful for Joplin's internal code.
|
||||
*
|
||||
* - Using the **optional** `assets` key you may specify assets such as JS or CSS that should be loaded in the rendered HTML document. Check for example the Joplin [Mermaid plugin](https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/lib/joplin-renderer/MdToHtml/rules/mermaid.ts) to see how the data should be structured.
|
||||
*
|
||||
* To include a regular Markdown-It plugin, that doesn't make use of any Joplin-specific features, you would simply create a file such as this:
|
||||
*
|
||||
* ```javascript
|
||||
* module.exports = {
|
||||
* default: function(context) {
|
||||
* return {
|
||||
* plugin: require('markdown-it-toc-done-right');
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
MarkdownItPlugin = 'markdownItPlugin',
|
||||
CodeMirrorPlugin = 'codeMirrorPlugin',
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import joplin from 'api';
|
|||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.views.menus.create('My Menu', [
|
||||
await joplin.views.menus.create('myMenu', 'My Menu', [
|
||||
{
|
||||
commandName: "newNote",
|
||||
},
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -31,14 +31,14 @@ joplin.plugins.register({
|
|||
});
|
||||
|
||||
// Add the first command to the note toolbar
|
||||
await joplin.views.toolbarButtons.create('testCommand1', ToolbarButtonLocation.NoteToolbar);
|
||||
await joplin.views.toolbarButtons.create('myButton1', 'testCommand1', ToolbarButtonLocation.NoteToolbar);
|
||||
|
||||
// Add the second command to the editor toolbar
|
||||
await joplin.views.toolbarButtons.create('testCommand2', ToolbarButtonLocation.EditorToolbar);
|
||||
await joplin.views.toolbarButtons.create('myButton2', 'testCommand2', ToolbarButtonLocation.EditorToolbar);
|
||||
|
||||
// Also add the commands to the menu
|
||||
await joplin.views.menuItems.create('testCommand1', MenuItemLocation.Tools, { accelerator: 'CmdOrCtrl+Alt+Shift+B' });
|
||||
await joplin.views.menuItems.create('testCommand2', MenuItemLocation.Tools);
|
||||
await joplin.views.menuItems.create('myMenuItem1', 'testCommand1', MenuItemLocation.Tools, { accelerator: 'CmdOrCtrl+Alt+Shift+B' });
|
||||
await joplin.views.menuItems.create('myMenuItem2', 'testCommand2', MenuItemLocation.Tools);
|
||||
|
||||
console.info('Running command with arguments...');
|
||||
const result = await joplin.commands.execute('commandWithResult', 'abcd', 123);
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ joplin.plugins.register({
|
|||
onStart: async function() {
|
||||
const panels = joplin.views.panels;
|
||||
|
||||
const view = await panels.create();
|
||||
const view = await (panels as any).create();
|
||||
|
||||
await panels.setHtml(view, 'Loading...');
|
||||
await panels.addScript(view, './webview.js');
|
||||
|
|
|
@ -11,6 +11,7 @@ yo joplin
|
|||
rsync -a --delete --exclude "src/" --exclude "package.json" --exclude "package-lock.json" --exclude "node_modules/" --exclude "dist/" "$TEMP_DIR/" "$SCRIPT_DIR/dialog/"
|
||||
rsync -a --delete --exclude "src/" --exclude "package.json" --exclude "package-lock.json" --exclude "node_modules/" --exclude "dist/" "$TEMP_DIR/" "$SCRIPT_DIR/events/"
|
||||
rsync -a --delete --exclude "src/" --exclude "package.json" --exclude "package-lock.json" --exclude "node_modules/" --exclude "dist/" "$TEMP_DIR/" "$SCRIPT_DIR/json_export/"
|
||||
rsync -a --delete --exclude "src/" --exclude "package.json" --exclude "package-lock.json" --exclude "node_modules/" --exclude "dist/" "$TEMP_DIR/" "$SCRIPT_DIR/menu/"
|
||||
rsync -a --delete --exclude "src/" --exclude "package.json" --exclude "package-lock.json" --exclude "node_modules/" --exclude "dist/" "$TEMP_DIR/" "$SCRIPT_DIR/multi_selection/"
|
||||
rsync -a --delete --exclude "src/" --exclude "package.json" --exclude "package-lock.json" --exclude "node_modules/" --exclude "dist/" "$TEMP_DIR/" "$SCRIPT_DIR/register_command/"
|
||||
rsync -a --delete --exclude "src/" --exclude "package.json" --exclude "package-lock.json" --exclude "node_modules/" --exclude "dist/" "$TEMP_DIR/" "$SCRIPT_DIR/selected_text/"
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class JoplinViewsDialogs {
|
|||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsMenuItems {
|
|||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ export default class JoplinViewsMenus {
|
|||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are displayed at the right of the sidebar and allows displaying any HTML content (within a webview) and update it in real-time. For example
|
||||
* it could be used to display a table of content for the active note, or display various metadata or graph.
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
|
@ -14,7 +17,7 @@ export default class JoplinViewsPanels {
|
|||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(): Promise<ViewHandle>;
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
|
|
|
@ -12,5 +12,5 @@ export default class JoplinViewsToolbarButtons {
|
|||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerS
|
|||
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
|
||||
import bridge from './services/bridge';
|
||||
import menuCommandNames from './gui/menuCommandNames';
|
||||
import { LayoutItem } from './gui/ResizableLayout/utils/types';
|
||||
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
|
||||
import ResourceService from '@joplin/lib/services/ResourceService';
|
||||
|
||||
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
||||
const MasterKey = require('@joplin/lib/models/MasterKey');
|
||||
|
@ -27,7 +30,6 @@ const Tag = require('@joplin/lib/models/Tag.js');
|
|||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const packageInfo = require('./packageInfo.js');
|
||||
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker');
|
||||
const ResourceService = require('@joplin/lib/services/ResourceService').default;
|
||||
const ClipperServer = require('@joplin/lib/ClipperServer');
|
||||
const ExternalEditWatcher = require('@joplin/lib/services/ExternalEditWatcher');
|
||||
const { webFrame } = require('electron');
|
||||
|
@ -67,6 +69,7 @@ const commands = [
|
|||
require('./gui/MainScreen/commands/openNote'),
|
||||
require('./gui/MainScreen/commands/openFolder'),
|
||||
require('./gui/MainScreen/commands/openTag'),
|
||||
require('./gui/MainScreen/commands/toggleLayoutMoveMode'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteBody'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteTitle'),
|
||||
require('./gui/NoteEditor/commands/showLocalSearch'),
|
||||
|
@ -105,17 +108,17 @@ export interface AppState extends State {
|
|||
route: AppStateRoute;
|
||||
navHistory: any[];
|
||||
noteVisiblePanes: string[];
|
||||
sidebarVisibility: boolean;
|
||||
noteListVisibility: boolean;
|
||||
windowContentSize: any;
|
||||
watchedNoteFiles: string[];
|
||||
lastEditorScrollPercents: any;
|
||||
devToolsVisible: boolean;
|
||||
visibleDialogs: any; // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: string;
|
||||
layoutMoveMode: boolean;
|
||||
|
||||
// Extra reducer keys go here
|
||||
watchedResources: any;
|
||||
mainLayout: LayoutItem;
|
||||
}
|
||||
|
||||
const appDefaultState: AppState = {
|
||||
|
@ -127,14 +130,14 @@ const appDefaultState: AppState = {
|
|||
},
|
||||
navHistory: [],
|
||||
noteVisiblePanes: ['editor', 'viewer'],
|
||||
sidebarVisibility: true,
|
||||
noteListVisibility: true,
|
||||
windowContentSize: bridge().windowContentSize(),
|
||||
watchedNoteFiles: [],
|
||||
lastEditorScrollPercents: {},
|
||||
devToolsVisible: false,
|
||||
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: null,
|
||||
layoutMoveMode: false,
|
||||
mainLayout: null,
|
||||
...resourceEditWatcherDefaultState,
|
||||
};
|
||||
|
||||
|
@ -234,25 +237,12 @@ class Application extends BaseApplication {
|
|||
newState.noteVisiblePanes = action.panes;
|
||||
break;
|
||||
|
||||
case 'SIDEBAR_VISIBILITY_TOGGLE':
|
||||
case 'MAIN_LAYOUT_SET':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.sidebarVisibility = !state.sidebarVisibility;
|
||||
break;
|
||||
|
||||
case 'SIDEBAR_VISIBILITY_SET':
|
||||
newState = Object.assign({}, state);
|
||||
newState.sidebarVisibility = action.visibility;
|
||||
break;
|
||||
|
||||
case 'NOTELIST_VISIBILITY_TOGGLE':
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteListVisibility = !state.noteListVisibility;
|
||||
break;
|
||||
|
||||
case 'NOTELIST_VISIBILITY_SET':
|
||||
newState = Object.assign({}, state);
|
||||
newState.noteListVisibility = action.visibility;
|
||||
newState = {
|
||||
...state,
|
||||
mainLayout: action.value,
|
||||
};
|
||||
break;
|
||||
|
||||
case 'NOTE_FILE_WATCHER_ADD':
|
||||
|
@ -333,6 +323,14 @@ class Application extends BaseApplication {
|
|||
}
|
||||
break;
|
||||
|
||||
case 'LAYOUT_MOVE_MODE_SET':
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
layoutMoveMode: action.value,
|
||||
};
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
||||
|
@ -384,14 +382,6 @@ class Application extends BaseApplication {
|
|||
Setting.setValue('noteVisiblePanes', newState.noteVisiblePanes);
|
||||
}
|
||||
|
||||
if (['SIDEBAR_VISIBILITY_TOGGLE', 'SIDEBAR_VISIBILITY_SET'].indexOf(action.type) >= 0) {
|
||||
Setting.setValue('sidebarVisibility', newState.sidebarVisibility);
|
||||
}
|
||||
|
||||
if (['NOTELIST_VISIBILITY_TOGGLE', 'NOTELIST_VISIBILITY_SET'].indexOf(action.type) >= 0) {
|
||||
Setting.setValue('noteListVisibility', newState.noteListVisibility);
|
||||
}
|
||||
|
||||
if (['NOTE_DEVTOOLS_TOGGLE', 'NOTE_DEVTOOLS_SET'].indexOf(action.type) >= 0) {
|
||||
this.toggleDevTools(newState.devToolsVisible);
|
||||
}
|
||||
|
@ -497,6 +487,37 @@ class Application extends BaseApplication {
|
|||
return cssString;
|
||||
}
|
||||
|
||||
private async initPluginService() {
|
||||
const pluginLogger = new Logger();
|
||||
pluginLogger.addTarget(TargetType.File, { path: `${Setting.value('profileDir')}/log-plugins.txt` });
|
||||
pluginLogger.addTarget(TargetType.Console, { prefix: 'Plugin Service:' });
|
||||
pluginLogger.setLevel(Setting.value('env') == 'dev' ? Logger.LEVEL_DEBUG : Logger.LEVEL_INFO);
|
||||
|
||||
const pluginRunner = new PluginRunner();
|
||||
PluginService.instance().setLogger(pluginLogger);
|
||||
PluginService.instance().initialize(PlatformImplementation.instance(), pluginRunner, this.store());
|
||||
|
||||
try {
|
||||
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) await PluginService.instance().loadAndRunPlugins(Setting.value('pluginDir'));
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('pluginDir')}:`, error);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Setting.value('plugins.devPluginPaths')) {
|
||||
const paths = Setting.value('plugins.devPluginPaths').split(',').map((p: string) => p.trim());
|
||||
await PluginService.instance().loadAndRunPlugins(paths);
|
||||
}
|
||||
|
||||
// Also load dev plugins that have passed via command line arguments
|
||||
if (Setting.value('startupDevPlugins')) {
|
||||
await PluginService.instance().loadAndRunPlugins(Setting.value('startupDevPlugins'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async start(argv: string[]): Promise<any> {
|
||||
const electronIsDev = require('electron-is-dev');
|
||||
|
||||
|
@ -539,7 +560,7 @@ class Application extends BaseApplication {
|
|||
|
||||
this.initRedux();
|
||||
|
||||
CommandService.instance().initialize(this.store(), Setting.value('env') == 'dev');
|
||||
CommandService.instance().initialize(this.store(), Setting.value('env') == 'dev', stateToWhenClauseContext);
|
||||
|
||||
for (const command of commands) {
|
||||
CommandService.instance().registerDeclaration(command.declaration);
|
||||
|
@ -688,34 +709,7 @@ class Application extends BaseApplication {
|
|||
|
||||
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
|
||||
|
||||
const pluginLogger = new Logger();
|
||||
pluginLogger.addTarget(TargetType.File, { path: `${Setting.value('profileDir')}/log-plugins.txt` });
|
||||
pluginLogger.addTarget(TargetType.Console, { prefix: 'Plugin Service:' });
|
||||
pluginLogger.setLevel(Setting.value('env') == 'dev' ? Logger.LEVEL_DEBUG : Logger.LEVEL_INFO);
|
||||
|
||||
const pluginRunner = new PluginRunner();
|
||||
PluginService.instance().setLogger(pluginLogger);
|
||||
PluginService.instance().initialize(PlatformImplementation.instance(), pluginRunner, this.store());
|
||||
|
||||
try {
|
||||
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) await PluginService.instance().loadAndRunPlugins(Setting.value('pluginDir'));
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('pluginDir')}:`, error);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Setting.value('plugins.devPluginPaths')) {
|
||||
const paths = Setting.value('plugins.devPluginPaths').split(',').map((p: string) => p.trim());
|
||||
await PluginService.instance().loadAndRunPlugins(paths);
|
||||
}
|
||||
|
||||
// Also load dev plugins that have passed via command line arguments
|
||||
if (Setting.value('startupDevPlugins')) {
|
||||
await PluginService.instance().loadAndRunPlugins(Setting.value('startupDevPlugins'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
|
||||
}
|
||||
await this.initPluginService();
|
||||
|
||||
this.setupContextMenu();
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Props {
|
|||
iconName?: string;
|
||||
level?: ButtonLevel;
|
||||
className?: string;
|
||||
onClick: Function;
|
||||
onClick?: Function;
|
||||
color?: string;
|
||||
iconAnimation?: string;
|
||||
tooltip?: string;
|
||||
|
@ -57,12 +57,14 @@ const StyledButtonPrimary = styled(StyledButtonBase)`
|
|||
border: none;
|
||||
background-color: ${(props: any) => props.theme.backgroundColor5};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorHover5};
|
||||
}
|
||||
${(props: any) => props.disabled} {
|
||||
&:hover {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorHover5};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorActive5};
|
||||
&:active {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorActive5};
|
||||
}
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
|
@ -78,12 +80,14 @@ const StyledButtonSecondary = styled(StyledButtonBase)`
|
|||
border: 1px solid ${(props: any) => props.theme.borderColor4};
|
||||
background-color: ${(props: any) => props.theme.backgroundColor4};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorHover4};
|
||||
}
|
||||
${(props: any) => props.disabled} {
|
||||
&:hover {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorHover4};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorActive4};
|
||||
&:active {
|
||||
background-color: ${(props: any) => props.theme.backgroundColorActive4};
|
||||
}
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import * as React from 'react';
|
||||
import ResizableLayout, { findItemByKey, LayoutItem, LayoutItemDirection, allDynamicSizes } from '../ResizableLayout/ResizableLayout';
|
||||
import NoteList from '../NoteList/NoteList';
|
||||
import ResizableLayout from '../ResizableLayout/ResizableLayout';
|
||||
import findItemByKey from '../ResizableLayout/utils/findItemByKey';
|
||||
import { MoveButtonClickEvent } from '../ResizableLayout/MoveButtons';
|
||||
import { move } from '../ResizableLayout/utils/movements';
|
||||
import { LayoutItem } from '../ResizableLayout/utils/types';
|
||||
import NoteEditor from '../NoteEditor/NoteEditor';
|
||||
import NoteContentPropertiesDialog from '../NoteContentPropertiesDialog';
|
||||
import ShareNoteDialog from '../ShareNoteDialog';
|
||||
import NoteListControls from '../NoteListControls/NoteListControls';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import { utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||
import SideBar from '../SideBar/SideBar';
|
||||
import UserWebview from '../../services/plugins/UserWebview';
|
||||
import UserWebviewDialog from '../../services/plugins/UserWebviewDialog';
|
||||
|
@ -15,20 +17,61 @@ import { ContainerType } from '@joplin/lib/services/plugins/WebviewController';
|
|||
import { stateUtils } from '@joplin/lib/reducer';
|
||||
import InteropServiceHelper from '../../InteropServiceHelper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import NoteListWrapper from '../NoteListWrapper/NoteListWrapper';
|
||||
import { AppState } from '../../app';
|
||||
import { saveLayout, loadLayout } from '../ResizableLayout/utils/persist';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import produce from 'immer';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import bridge from '../../services/bridge';
|
||||
import time from '@joplin/lib/time';
|
||||
import styled from 'styled-components';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import validateLayout from '../ResizableLayout/utils/validateLayout';
|
||||
import iterateItems from '../ResizableLayout/utils/iterateItems';
|
||||
import removeItem from '../ResizableLayout/utils/removeItem';
|
||||
|
||||
const produce = require('immer').default;
|
||||
const { connect } = require('react-redux');
|
||||
const { PromptDialog } = require('../PromptDialog.min.js');
|
||||
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const { themeStyle } = require('@joplin/lib/theme.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const PluginManager = require('@joplin/lib/services/PluginManager');
|
||||
const EncryptionService = require('@joplin/lib/services/EncryptionService');
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
interface LayerModalState {
|
||||
visible: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
plugins: PluginStates;
|
||||
pluginsLoaded: boolean;
|
||||
hasNotesBeingSaved: boolean;
|
||||
dispatch: Function;
|
||||
mainLayout: LayoutItem;
|
||||
style: any;
|
||||
layoutMoveMode: boolean;
|
||||
editorNoteStatuses: any;
|
||||
customCss: string;
|
||||
shouldUpgradeSyncTarget: boolean;
|
||||
hasDisabledSyncItems: boolean;
|
||||
hasDisabledEncryptionItems: boolean;
|
||||
showMissingMasterKeyMessage: boolean;
|
||||
showNeedUpgradingMasterKeyMessage: boolean;
|
||||
showShouldReencryptMessage: boolean;
|
||||
focusedField: string;
|
||||
themeId: number;
|
||||
settingEditorCodeView: boolean;
|
||||
pluginsLegacy: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
promptOptions: any;
|
||||
modalLayer: LayerModalState;
|
||||
notePropertiesDialogOptions: any;
|
||||
noteContentPropertiesDialogOptions: any;
|
||||
shareNoteDialogOptions: any;
|
||||
}
|
||||
|
||||
const StyledUserWebviewDialogContainer = styled.div`
|
||||
display: flex;
|
||||
|
@ -41,6 +84,15 @@ const StyledUserWebviewDialogContainer = styled.div`
|
|||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const defaultLayout: LayoutItem = {
|
||||
key: 'root',
|
||||
children: [
|
||||
{ key: 'sideBar', width: 250 },
|
||||
{ key: 'noteList', width: 250 },
|
||||
{ key: 'editor' },
|
||||
],
|
||||
};
|
||||
|
||||
const commands = [
|
||||
require('./commands/editAlarm'),
|
||||
require('./commands/exportPdf'),
|
||||
|
@ -65,20 +117,21 @@ const commands = [
|
|||
require('./commands/toggleNoteList'),
|
||||
require('./commands/toggleSideBar'),
|
||||
require('./commands/toggleVisiblePanes'),
|
||||
require('./commands/toggleLayoutMoveMode'),
|
||||
require('./commands/openNote'),
|
||||
require('./commands/openFolder'),
|
||||
require('./commands/openTag'),
|
||||
];
|
||||
|
||||
class MainScreenComponent extends React.Component<any, any> {
|
||||
class MainScreenComponent extends React.Component<Props, State> {
|
||||
|
||||
waitForNotesSavedIID_: any;
|
||||
isPrinting_: boolean;
|
||||
styleKey_: string;
|
||||
styles_: any;
|
||||
promptOnClose_: Function;
|
||||
private waitForNotesSavedIID_: any;
|
||||
private isPrinting_: boolean;
|
||||
private styleKey_: string;
|
||||
private styles_: any;
|
||||
private promptOnClose_: Function;
|
||||
|
||||
constructor(props: any) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -90,9 +143,10 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
notePropertiesDialogOptions: {},
|
||||
noteContentPropertiesDialogOptions: {},
|
||||
shareNoteDialogOptions: {},
|
||||
layout: this.buildLayout(props.plugins),
|
||||
};
|
||||
|
||||
this.updateMainLayout(this.buildLayout(props.plugins));
|
||||
|
||||
this.registerCommands();
|
||||
|
||||
this.setupAppCloseHandling();
|
||||
|
@ -103,117 +157,72 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
this.userWebview_message = this.userWebview_message.bind(this);
|
||||
this.resizableLayout_resize = this.resizableLayout_resize.bind(this);
|
||||
this.resizableLayout_renderItem = this.resizableLayout_renderItem.bind(this);
|
||||
this.resizableLayout_moveButtonClick = this.resizableLayout_moveButtonClick.bind(this);
|
||||
this.window_resize = this.window_resize.bind(this);
|
||||
this.rowHeight = this.rowHeight.bind(this);
|
||||
this.layoutModeListenerKeyDown = this.layoutModeListenerKeyDown.bind(this);
|
||||
|
||||
window.addEventListener('resize', this.window_resize);
|
||||
}
|
||||
|
||||
buildLayout(plugins: any): LayoutItem {
|
||||
const rootLayoutSize = this.rootLayoutSize();
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const sideBarMinWidth = 200;
|
||||
|
||||
const sizes = {
|
||||
sideBarColumn: {
|
||||
width: 150,
|
||||
},
|
||||
noteListColumn: {
|
||||
width: 150,
|
||||
},
|
||||
pluginColumn: {
|
||||
width: 150,
|
||||
},
|
||||
...Setting.value('ui.layout'),
|
||||
};
|
||||
|
||||
for (const k in sizes) {
|
||||
if (sizes[k].width < sideBarMinWidth) sizes[k].width = sideBarMinWidth;
|
||||
}
|
||||
|
||||
const pluginColumnChildren: LayoutItem[] = [];
|
||||
|
||||
private updateLayoutPluginViews(layout: LayoutItem, plugins: PluginStates) {
|
||||
const infos = pluginUtils.viewInfosByType(plugins, 'webview');
|
||||
|
||||
for (const info of infos) {
|
||||
if (info.view.containerType !== ContainerType.Panel) continue;
|
||||
let newLayout = produce(layout, (draftLayout: LayoutItem) => {
|
||||
for (const info of infos) {
|
||||
if (info.view.containerType !== ContainerType.Panel) continue;
|
||||
|
||||
// For now it's assumed all views go in the "pluginColumn" so they are
|
||||
// resizable vertically. But horizontally they stretch 100%
|
||||
const viewId = info.view.id;
|
||||
const viewId = info.view.id;
|
||||
const existingItem = findItemByKey(draftLayout, viewId);
|
||||
|
||||
const size = {
|
||||
...(sizes[viewId] ? sizes[viewId] : null),
|
||||
width: '100%',
|
||||
};
|
||||
if (!existingItem) {
|
||||
draftLayout.children.push({
|
||||
key: viewId,
|
||||
context: {
|
||||
pluginId: info.plugin.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pluginColumnChildren.push({
|
||||
key: viewId,
|
||||
resizableBottom: true,
|
||||
context: {
|
||||
plugin: info.plugin,
|
||||
control: info.view,
|
||||
},
|
||||
...size,
|
||||
});
|
||||
// Remove layout items that belong to plugins that are no longer
|
||||
// active.
|
||||
const pluginIds = Object.keys(plugins);
|
||||
const itemsToRemove: string[] = [];
|
||||
iterateItems(newLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
||||
if (item.context && item.context.pluginId && !pluginIds.includes(item.context.pluginId)) {
|
||||
itemsToRemove.push(item.key);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
for (const itemKey of itemsToRemove) {
|
||||
newLayout = removeItem(newLayout, itemKey);
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'root',
|
||||
direction: LayoutItemDirection.Row,
|
||||
width: rootLayoutSize.width,
|
||||
height: rootLayoutSize.height,
|
||||
children: [
|
||||
{
|
||||
key: 'sideBarColumn',
|
||||
direction: LayoutItemDirection.Column,
|
||||
resizableRight: true,
|
||||
width: sizes.sideBarColumn.width,
|
||||
visible: Setting.value('sidebarVisibility'),
|
||||
minWidth: sideBarMinWidth,
|
||||
children: [
|
||||
{
|
||||
key: 'sideBar',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'noteListColumn',
|
||||
direction: LayoutItemDirection.Column,
|
||||
resizableRight: true,
|
||||
width: sizes.noteListColumn.width,
|
||||
visible: Setting.value('noteListVisibility'),
|
||||
minWidth: sideBarMinWidth,
|
||||
children: [
|
||||
{
|
||||
height: theme.topRowHeight,
|
||||
key: 'noteListControls',
|
||||
},
|
||||
{
|
||||
key: 'noteList',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'pluginColumn',
|
||||
direction: LayoutItemDirection.Column,
|
||||
resizableRight: true,
|
||||
width: sizes.pluginColumn.width,
|
||||
visible: !!pluginColumnChildren.length,
|
||||
minWidth: sideBarMinWidth,
|
||||
children: pluginColumnChildren,
|
||||
},
|
||||
{
|
||||
key: 'editorColumn',
|
||||
direction: LayoutItemDirection.Column,
|
||||
children: [
|
||||
{
|
||||
key: 'editor',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
return newLayout !== layout ? validateLayout(newLayout) : layout;
|
||||
}
|
||||
|
||||
private buildLayout(plugins: PluginStates): LayoutItem {
|
||||
const rootLayoutSize = this.rootLayoutSize();
|
||||
|
||||
const userLayout = Setting.value('ui.layout');
|
||||
let output = null;
|
||||
|
||||
try {
|
||||
output = loadLayout(userLayout, defaultLayout, rootLayoutSize);
|
||||
|
||||
if (!findItemByKey(output, 'sideBar') || !findItemByKey(output, 'noteList') || !findItemByKey(output, 'editor')) {
|
||||
throw new Error('"sideBar", "noteList" and "editor" must be present in the layout');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not load layout - restoring default layout:', error);
|
||||
console.warn('Layout was:', userLayout);
|
||||
output = loadLayout(null, defaultLayout, rootLayoutSize);
|
||||
}
|
||||
|
||||
return this.updateLayoutPluginViews(output, plugins);
|
||||
}
|
||||
|
||||
window_resize() {
|
||||
|
@ -263,25 +272,22 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
this.setState({ shareNoteDialogOptions: {} });
|
||||
}
|
||||
|
||||
updateMainLayout(layout: LayoutItem) {
|
||||
this.props.dispatch({
|
||||
type: 'MAIN_LAYOUT_SET',
|
||||
value: layout,
|
||||
});
|
||||
}
|
||||
|
||||
updateRootLayoutSize() {
|
||||
this.setState({ layout: produce(this.state.layout, (draft: any) => {
|
||||
this.updateMainLayout(produce(this.props.mainLayout, (draft: any) => {
|
||||
const s = this.rootLayoutSize();
|
||||
draft.width = s.width;
|
||||
draft.height = s.height;
|
||||
}) });
|
||||
}));
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: any, prevState: any) {
|
||||
if (this.props.noteListVisibility !== prevProps.noteListVisibility || this.props.sidebarVisibility !== prevProps.sidebarVisibility) {
|
||||
this.setState({ layout: produce(this.state.layout, (draft: any) => {
|
||||
const noteListColumn = findItemByKey(draft, 'noteListColumn');
|
||||
noteListColumn.visible = this.props.noteListVisibility;
|
||||
|
||||
const sideBarColumn = findItemByKey(draft, 'sideBarColumn');
|
||||
sideBarColumn.visible = this.props.sidebarVisibility;
|
||||
}) });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
if (prevProps.style.width !== this.props.style.width ||
|
||||
prevProps.style.height !== this.props.style.height ||
|
||||
this.messageBoxVisible(prevProps) !== this.messageBoxVisible(this.props)
|
||||
|
@ -290,7 +296,8 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
}
|
||||
|
||||
if (prevProps.plugins !== this.props.plugins) {
|
||||
this.setState({ layout: this.buildLayout(this.props.plugins) });
|
||||
this.updateMainLayout(this.updateLayoutPluginViews(this.props.mainLayout, this.props.plugins));
|
||||
// this.setState({ layout: this.buildLayout(this.props.plugins) });
|
||||
}
|
||||
|
||||
if (this.state.notePropertiesDialogOptions !== prevState.notePropertiesDialogOptions) {
|
||||
|
@ -313,28 +320,28 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
name: 'shareNote',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.mainLayout !== prevProps.mainLayout) {
|
||||
const toSave = saveLayout(this.props.mainLayout);
|
||||
Setting.setValue('ui.layout', toSave);
|
||||
}
|
||||
}
|
||||
|
||||
layoutModeListenerKeyDown(event: any) {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (!this.props.layoutMoveMode) return;
|
||||
CommandService.instance().execute('toggleLayoutMoveMode');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateRootLayoutSize();
|
||||
window.addEventListener('keydown', this.layoutModeListenerKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unregisterCommands();
|
||||
|
||||
window.removeEventListener('resize', this.window_resize);
|
||||
}
|
||||
|
||||
toggleSideBar() {
|
||||
this.props.dispatch({
|
||||
type: 'SIDEBAR_VISIBILITY_TOGGLE',
|
||||
});
|
||||
}
|
||||
|
||||
toggleNoteList() {
|
||||
this.props.dispatch({
|
||||
type: 'NOTELIST_VISIBILITY_TOGGLE',
|
||||
});
|
||||
window.removeEventListener('keydown', this.layoutModeListenerKeyDown);
|
||||
}
|
||||
|
||||
async waitForNoteToSaved(noteId: string) {
|
||||
|
@ -399,8 +406,8 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
return 50;
|
||||
}
|
||||
|
||||
styles(themeId: number, width: number, height: number, messageBoxVisible: boolean, isSidebarVisible: any, isNoteListVisible: any) {
|
||||
const styleKey = [themeId, width, height, messageBoxVisible, +isSidebarVisible, +isNoteListVisible].join('_');
|
||||
styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
|
||||
const styleKey = [themeId, width, height, messageBoxVisible].join('_');
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
const theme = themeStyle(themeId);
|
||||
|
@ -561,30 +568,57 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
}
|
||||
|
||||
resizableLayout_resize(event: any) {
|
||||
this.setState({ layout: event.layout });
|
||||
Setting.setValue('ui.layout', allDynamicSizes(event.layout));
|
||||
this.updateMainLayout(event.layout);
|
||||
}
|
||||
|
||||
resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
|
||||
const newLayout = move(this.props.mainLayout, event.itemKey, event.direction);
|
||||
this.updateMainLayout(newLayout);
|
||||
}
|
||||
|
||||
resizableLayout_renderItem(key: string, event: any) {
|
||||
const eventEmitter = event.eventEmitter;
|
||||
|
||||
if (key === 'sideBar') {
|
||||
return <SideBar key={key} />;
|
||||
} else if (key === 'noteList') {
|
||||
return <NoteList key={key} resizableLayoutEventEmitter={eventEmitter} size={event.size} visible={event.visible}/>;
|
||||
} else if (key === 'editor') {
|
||||
const bodyEditor = this.props.settingEditorCodeView ? 'CodeMirror' : 'TinyMCE';
|
||||
return <NoteEditor key={key} bodyEditor={bodyEditor} />;
|
||||
} else if (key === 'noteListControls') {
|
||||
return <NoteListControls key={key} showNewNoteButtons={this.props.focusedField !== 'globalSearch'} />;
|
||||
} else if (key.indexOf('plugin-view') === 0) {
|
||||
const { control, plugin } = event.item.context;
|
||||
const components: any = {
|
||||
sideBar: () => {
|
||||
return <SideBar key={key} />;
|
||||
},
|
||||
|
||||
noteList: () => {
|
||||
return <NoteListWrapper
|
||||
key={key}
|
||||
resizableLayoutEventEmitter={eventEmitter}
|
||||
visible={event.visible}
|
||||
focusedField={this.props.focusedField}
|
||||
size={event.size}
|
||||
themeId={this.props.themeId}
|
||||
/>;
|
||||
},
|
||||
|
||||
editor: () => {
|
||||
const bodyEditor = this.props.settingEditorCodeView ? 'CodeMirror' : 'TinyMCE';
|
||||
return <NoteEditor key={key} bodyEditor={bodyEditor} />;
|
||||
},
|
||||
};
|
||||
|
||||
if (components[key]) return components[key]();
|
||||
|
||||
if (key.indexOf('plugin-view') === 0) {
|
||||
const viewInfo = pluginUtils.viewInfoByViewId(this.props.plugins, event.item.key);
|
||||
|
||||
if (!viewInfo) {
|
||||
console.warn(`Could not find plugin associated with view: ${event.item.key}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { view, plugin } = viewInfo;
|
||||
|
||||
return <UserWebview
|
||||
key={control.id}
|
||||
viewId={control.id}
|
||||
key={view.id}
|
||||
viewId={view.id}
|
||||
themeId={this.props.themeId}
|
||||
html={control.html}
|
||||
scripts={control.scripts}
|
||||
html={view.html}
|
||||
scripts={view.scripts}
|
||||
pluginId={plugin.id}
|
||||
onMessage={this.userWebview_message}
|
||||
borderBottom={true}
|
||||
|
@ -635,9 +669,7 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
this.props.style
|
||||
);
|
||||
const promptOptions = this.state.promptOptions;
|
||||
const sidebarVisibility = this.props.sidebarVisibility;
|
||||
const noteListVisibility = this.props.noteListVisibility;
|
||||
const styles = this.styles(this.props.themeId, style.width, style.height, this.messageBoxVisible(), sidebarVisibility, noteListVisibility);
|
||||
const styles = this.styles(this.props.themeId, style.width, style.height, this.messageBoxVisible());
|
||||
|
||||
if (!this.promptOnClose_) {
|
||||
this.promptOnClose_ = (answer: any, buttonType: any) => {
|
||||
|
@ -656,6 +688,18 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
const noteContentPropertiesDialogOptions = this.state.noteContentPropertiesDialogOptions;
|
||||
const shareNoteDialogOptions = this.state.shareNoteDialogOptions;
|
||||
|
||||
const layoutComp = this.props.mainLayout ? (
|
||||
<ResizableLayout
|
||||
height={styles.rowHeight}
|
||||
layout={this.props.mainLayout}
|
||||
onResize={this.resizableLayout_resize}
|
||||
onMoveButtonClick={this.resizableLayout_moveButtonClick}
|
||||
renderItem={this.resizableLayout_renderItem}
|
||||
moveMode={this.props.layoutMoveMode}
|
||||
moveModeMessage={_('Use the arrows to move the layout items. Press "Escape" to exit.')}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
|
||||
|
@ -667,25 +711,17 @@ class MainScreenComponent extends React.Component<any, any> {
|
|||
<PromptDialog autocomplete={promptOptions && 'autocomplete' in promptOptions ? promptOptions.autocomplete : null} defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''} themeId={this.props.themeId} style={styles.prompt} onClose={this.promptOnClose_} label={promptOptions ? promptOptions.label : ''} description={promptOptions ? promptOptions.description : null} visible={!!this.state.promptOptions} buttons={promptOptions && 'buttons' in promptOptions ? promptOptions.buttons : null} inputType={promptOptions && 'inputType' in promptOptions ? promptOptions.inputType : null} />
|
||||
|
||||
{messageComp}
|
||||
<ResizableLayout
|
||||
width={this.state.width}
|
||||
height={styles.rowHeight}
|
||||
layout={this.state.layout}
|
||||
onResize={this.resizableLayout_resize}
|
||||
renderItem={this.resizableLayout_renderItem}
|
||||
/>
|
||||
{layoutComp}
|
||||
{pluginDialog}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => {
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
themeId: state.settings.theme,
|
||||
settingEditorCodeView: state.settings['editor.codeView'],
|
||||
sidebarVisibility: state.sidebarVisibility,
|
||||
noteListVisibility: state.noteListVisibility,
|
||||
folders: state.folders,
|
||||
notes: state.notes,
|
||||
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
||||
|
@ -703,6 +739,8 @@ const mapStateToProps = (state: any) => {
|
|||
editorNoteStatuses: state.editorNoteStatuses,
|
||||
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
|
||||
focusedField: state.focusedField,
|
||||
layoutMoveMode: state.layoutMoveMode,
|
||||
mainLayout: state.mainLayout,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { AppState } from '../../../app';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleLayoutMoveMode',
|
||||
label: () => _('Change application layout'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, value: boolean = null) => {
|
||||
const newValue = value !== null ? value : !(context.state as AppState).layoutMoveMode;
|
||||
context.dispatch({
|
||||
type: 'LAYOUT_MOVE_MODE_SET',
|
||||
value: newValue,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,5 +1,8 @@
|
|||
import { CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import setLayoutItemProps from '../../ResizableLayout/utils/setLayoutItemProps';
|
||||
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
||||
import { AppState } from '../../../app';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleNoteList',
|
||||
|
@ -7,11 +10,18 @@ export const declaration: CommandDeclaration = {
|
|||
iconName: 'fas fa-align-justify',
|
||||
};
|
||||
|
||||
export const runtime = (comp: any): CommandRuntime => {
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async () => {
|
||||
comp.props.dispatch({
|
||||
type: 'NOTELIST_VISIBILITY_TOGGLE',
|
||||
execute: async (context: CommandContext) => {
|
||||
const layout = (context.state as AppState).mainLayout;
|
||||
|
||||
const newLayout = setLayoutItemProps(layout, 'noteList', {
|
||||
visible: !layoutItemProp(layout, 'noteList', 'visible'),
|
||||
});
|
||||
|
||||
context.dispatch({
|
||||
type: 'MAIN_LAYOUT_SET',
|
||||
value: newLayout,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import setLayoutItemProps from '../../ResizableLayout/utils/setLayoutItemProps';
|
||||
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
|
||||
import { AppState } from '../../../app';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleSideBar',
|
||||
|
@ -7,11 +10,18 @@ export const declaration: CommandDeclaration = {
|
|||
iconName: 'fas fa-bars',
|
||||
};
|
||||
|
||||
export const runtime = (comp: any): CommandRuntime => {
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async () => {
|
||||
comp.props.dispatch({
|
||||
type: 'SIDEBAR_VISIBILITY_TOGGLE',
|
||||
execute: async (context: CommandContext) => {
|
||||
const layout = (context.state as AppState).mainLayout;
|
||||
|
||||
const newLayout = setLayoutItemProps(layout, 'sideBar', {
|
||||
visible: !layoutItemProp(layout, 'sideBar', 'visible'),
|
||||
});
|
||||
|
||||
context.dispatch({
|
||||
type: 'MAIN_LAYOUT_SET',
|
||||
value: newLayout,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -13,9 +13,9 @@ import { Module } from '@joplin/lib/services/interop/types';
|
|||
import InteropServiceHelper from '../InteropServiceHelper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { MenuItem, MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
|
||||
import stateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
|
||||
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
|
||||
import menuCommandNames from './menuCommandNames';
|
||||
import stateToWhenClauseContext from '../services/commands/stateToWhenClauseContext';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
|
@ -519,6 +519,8 @@ function useMenu(props: Props) {
|
|||
view: {
|
||||
label: _('&View'),
|
||||
submenu: [
|
||||
menuItemDic.toggleLayoutMoveMode,
|
||||
separator(),
|
||||
menuItemDic.toggleSideBar,
|
||||
menuItemDic.toggleNoteList,
|
||||
menuItemDic.toggleVisiblePanes,
|
||||
|
|
|
@ -5,7 +5,7 @@ import { utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
|||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../../../../app';
|
||||
import ToolbarButtonUtils, { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import stateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
|
||||
import stateToWhenClauseContext from '../../../../services/commands/stateToWhenClauseContext';
|
||||
const { buildStyle } = require('@joplin/lib/theme');
|
||||
|
||||
interface ToolbarProps {
|
||||
|
|
|
@ -23,12 +23,12 @@ import eventManager from '@joplin/lib/eventManager';
|
|||
import { AppState } from '../../app';
|
||||
import ToolbarButtonUtils from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import stateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
|
||||
import TagList from '../TagList';
|
||||
import NoteTitleBar from './NoteTitle/NoteTitleBar';
|
||||
import markupLanguageUtils from '@joplin/lib/markupLanguageUtils';
|
||||
import usePrevious from '../hooks/usePrevious';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
||||
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { substrWithEllipsis } = require('@joplin/lib/string-utils');
|
||||
|
|
|
@ -8,10 +8,11 @@ const styled = require('styled-components').default;
|
|||
|
||||
interface Props {
|
||||
showNewNoteButtons: boolean;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
width: 100%;
|
||||
height: ${(props: any) => props.height}px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: ${(props: any) => props.theme.mainPadding}px;
|
||||
|
@ -68,7 +69,7 @@ export default function NoteListControls(props: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<StyledRoot height={props.height}>
|
||||
<SearchBar inputRef={searchBarRef}/>
|
||||
{renderNewNoteButtons()}
|
||||
</StyledRoot>
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import NoteList from '../NoteList/NoteList';
|
||||
import NoteListControls from '../NoteListControls/NoteListControls';
|
||||
import { Size } from '../ResizableLayout/utils/types';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
resizableLayoutEventEmitter: any;
|
||||
size: Size;
|
||||
visible: boolean;
|
||||
focusedField: string;
|
||||
themeId: number;
|
||||
}
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export default function NoteListWrapper(props: Props) {
|
||||
const theme = themeStyle(props.themeId);
|
||||
const controlHeight = theme.topRowHeight;
|
||||
|
||||
const noteListSize = useMemo(() => {
|
||||
return {
|
||||
width: props.size.width,
|
||||
height: props.size.height - controlHeight,
|
||||
};
|
||||
}, [props.size, controlHeight]);
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<NoteListControls showNewNoteButtons={props.focusedField !== 'globalSearch'} height={controlHeight} />
|
||||
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
|
@ -3,7 +3,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
|||
import ToolbarBase from '../ToolbarBase';
|
||||
import { utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||
import ToolbarButtonUtils, { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import stateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
|
||||
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
||||
const { connect } = require('react-redux');
|
||||
const { buildStyle } = require('@joplin/lib/theme');
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
import { MoveDirection } from './utils/movements';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
background-color: ${props => props.theme.backgroundColor};
|
||||
border-radius: 5px;
|
||||
`;
|
||||
|
||||
const ButtonRow = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const EmptyButton = styled(Button)`
|
||||
visibility: hidden;
|
||||
`;
|
||||
|
||||
const ArrowButton = styled(Button)`
|
||||
opacity: ${props => props.disabled ? 0.2 : 1};
|
||||
`;
|
||||
|
||||
export interface MoveButtonClickEvent {
|
||||
direction: MoveDirection;
|
||||
itemKey: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onClick(event: MoveButtonClickEvent): void;
|
||||
itemKey: string;
|
||||
canMoveLeft: boolean;
|
||||
canMoveRight: boolean;
|
||||
canMoveUp: boolean;
|
||||
canMoveDown: boolean;
|
||||
}
|
||||
|
||||
export default function MoveButtons(props: Props) {
|
||||
const onButtonClick = useCallback((direction: MoveDirection) => {
|
||||
props.onClick({ direction, itemKey: props.itemKey });
|
||||
}, [props.onClick, props.itemKey]);
|
||||
|
||||
function canMove(dir: MoveDirection) {
|
||||
if (dir === MoveDirection.Up) return props.canMoveUp;
|
||||
if (dir === MoveDirection.Down) return props.canMoveDown;
|
||||
if (dir === MoveDirection.Left) return props.canMoveLeft;
|
||||
if (dir === MoveDirection.Right) return props.canMoveRight;
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
|
||||
function renderButton(dir: MoveDirection) {
|
||||
return <ArrowButton
|
||||
disabled={!canMove(dir)}
|
||||
level={ButtonLevel.Primary}
|
||||
iconName={`fas fa-arrow-${dir}`}
|
||||
onClick={() => onButtonClick(dir)}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<ButtonRow>
|
||||
{renderButton(MoveDirection.Up)}
|
||||
</ButtonRow>
|
||||
<ButtonRow>
|
||||
{renderButton(MoveDirection.Left)}
|
||||
<EmptyButton iconName="fas fa-arrow-down" disabled={true}/>
|
||||
{renderButton(MoveDirection.Right)}
|
||||
</ButtonRow>
|
||||
<ButtonRow>
|
||||
{renderButton(MoveDirection.Down)}
|
||||
</ButtonRow>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
|
@ -1,36 +1,18 @@
|
|||
import * as React from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import produce from 'immer';
|
||||
import useWindowResizeEvent from './hooks/useWindowResizeEvent';
|
||||
import useLayoutItemSizes, { LayoutItemSizes, itemSize } from './hooks/useLayoutItemSizes';
|
||||
const { Resizable } = require('re-resizable');
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import useWindowResizeEvent from './utils/useWindowResizeEvent';
|
||||
import setLayoutItemProps from './utils/setLayoutItemProps';
|
||||
import useLayoutItemSizes, { LayoutItemSizes, itemSize } from './utils/useLayoutItemSizes';
|
||||
import validateLayout from './utils/validateLayout';
|
||||
import { Size, LayoutItem } from './utils/types';
|
||||
import { canMove, MoveDirection } from './utils/movements';
|
||||
import MoveButtons, { MoveButtonClickEvent } from './MoveButtons';
|
||||
import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootWrapper, MoveModeRootMessage } from './utils/style';
|
||||
import { Resizable } from 're-resizable';
|
||||
const EventEmitter = require('events');
|
||||
|
||||
export const dragBarThickness = 5;
|
||||
|
||||
export enum LayoutItemDirection {
|
||||
Row = 'row',
|
||||
Column = 'column',
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface LayoutItem {
|
||||
key: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
minWidth?: number;
|
||||
minHeight?: number;
|
||||
children?: LayoutItem[];
|
||||
direction?: LayoutItemDirection;
|
||||
resizableRight?: boolean;
|
||||
resizableBottom?: boolean;
|
||||
visible?: boolean;
|
||||
context?: any;
|
||||
}
|
||||
const itemMinWidth = 20;
|
||||
const itemMinHeight = 20;
|
||||
|
||||
interface onResizeEvent {
|
||||
layout: LayoutItem;
|
||||
|
@ -42,78 +24,24 @@ interface Props {
|
|||
width?: number;
|
||||
height?: number;
|
||||
renderItem: Function;
|
||||
onMoveButtonClick(event: MoveButtonClickEvent): void;
|
||||
moveMode: boolean;
|
||||
moveModeMessage: string;
|
||||
}
|
||||
|
||||
export function allDynamicSizes(layout: LayoutItem): any {
|
||||
const output: any = {};
|
||||
|
||||
function recurseProcess(item: LayoutItem) {
|
||||
if (item.resizableBottom || item.resizableRight) {
|
||||
if ('width' in item || 'height' in item) {
|
||||
const size: any = {};
|
||||
if ('width' in item) size.width = item.width;
|
||||
if ('height' in item) size.height = item.height;
|
||||
output[item.key] = size;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
recurseProcess(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurseProcess(layout);
|
||||
|
||||
return output;
|
||||
function itemVisible(item: LayoutItem, moveMode: boolean) {
|
||||
if (moveMode) return true;
|
||||
if (item.children && !item.children.length) return false;
|
||||
return item.visible !== false;
|
||||
}
|
||||
|
||||
export function findItemByKey(layout: LayoutItem, key: string): LayoutItem {
|
||||
function recurseFind(item: LayoutItem): LayoutItem {
|
||||
if (item.key === key) return item;
|
||||
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
const found = recurseFind(child);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const output = recurseFind(layout);
|
||||
if (!output) throw new Error(`Invalid item key: ${key}`);
|
||||
return output;
|
||||
}
|
||||
|
||||
function updateLayoutItem(layout: LayoutItem, key: string, props: any) {
|
||||
return produce(layout, (draftState: LayoutItem) => {
|
||||
function recurseFind(item: LayoutItem) {
|
||||
if (item.key === key) {
|
||||
for (const n in props) {
|
||||
(item as any)[n] = props[n];
|
||||
}
|
||||
} else {
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
recurseFind(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurseFind(draftState);
|
||||
});
|
||||
}
|
||||
|
||||
function renderContainer(item: LayoutItem, sizes: LayoutItemSizes, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean): any {
|
||||
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
|
||||
const style: any = {
|
||||
display: item.visible !== false ? 'flex' : 'none',
|
||||
display: itemVisible(item, moveMode) ? 'flex' : 'none',
|
||||
flexDirection: item.direction,
|
||||
};
|
||||
|
||||
const size: Size = itemSize(item, sizes);
|
||||
const size: Size = itemSize(item, parent, sizes, true);
|
||||
|
||||
const className = `resizableLayoutItem rli-${item.key}`;
|
||||
if (item.resizableRight || item.resizableBottom) {
|
||||
|
@ -128,21 +56,18 @@ function renderContainer(item: LayoutItem, sizes: LayoutItemSizes, onResizeStart
|
|||
topLeft: false,
|
||||
};
|
||||
|
||||
if (item.resizableRight) style.paddingRight = dragBarThickness;
|
||||
if (item.resizableBottom) style.paddingBottom = dragBarThickness;
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
key={item.key}
|
||||
className={className}
|
||||
style={style}
|
||||
size={size}
|
||||
onResizeStart={onResizeStart}
|
||||
onResize={onResize}
|
||||
onResizeStop={onResizeStop}
|
||||
onResizeStart={onResizeStart as any}
|
||||
onResize={onResize as any}
|
||||
onResizeStop={onResizeStop as any}
|
||||
enable={enable}
|
||||
minWidth={item.minWidth}
|
||||
minHeight={item.minHeight}
|
||||
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
|
||||
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
|
||||
>
|
||||
{children}
|
||||
</Resizable>
|
||||
|
@ -161,8 +86,29 @@ function ResizableLayout(props: Props) {
|
|||
|
||||
const [resizedItem, setResizedItem] = useState<any>(null);
|
||||
|
||||
function renderLayoutItem(item: LayoutItem, sizes: LayoutItemSizes, isVisible: boolean, isLastChild: boolean): any {
|
||||
function renderItemWrapper(comp: any, item: LayoutItem, parent: LayoutItem | null, size: Size, moveMode: boolean) {
|
||||
const moveOverlay = moveMode ? (
|
||||
<StyledMoveOverlay>
|
||||
<MoveButtons
|
||||
itemKey={item.key}
|
||||
onClick={props.onMoveButtonClick}
|
||||
canMoveLeft={canMove(MoveDirection.Left, item, parent)}
|
||||
canMoveRight={canMove(MoveDirection.Right, item, parent)}
|
||||
canMoveUp={canMove(MoveDirection.Up, item, parent)}
|
||||
canMoveDown={canMove(MoveDirection.Down, item, parent)}
|
||||
/>
|
||||
</StyledMoveOverlay>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<StyledWrapperRoot key={item.key} size={size}>
|
||||
{moveOverlay}
|
||||
{comp}
|
||||
</StyledWrapperRoot>
|
||||
);
|
||||
}
|
||||
|
||||
function renderLayoutItem(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, isVisible: boolean, isLastChild: boolean): any {
|
||||
function onResizeStart() {
|
||||
setResizedItem({
|
||||
key: item.key,
|
||||
|
@ -171,45 +117,79 @@ function ResizableLayout(props: Props) {
|
|||
});
|
||||
}
|
||||
|
||||
function onResize(_event: any, _direction: any, _refToElement: HTMLDivElement, delta: any) {
|
||||
const newLayout = updateLayoutItem(props.layout, item.key, {
|
||||
width: resizedItem.initialWidth + delta.width,
|
||||
height: resizedItem.initialHeight + delta.height,
|
||||
});
|
||||
function onResize(_event: any, direction: string, _refToElement: any, delta: any) {
|
||||
const newWidth = Math.max(itemMinWidth, resizedItem.initialWidth + delta.width);
|
||||
const newHeight = Math.max(itemMinHeight, resizedItem.initialHeight + delta.height);
|
||||
|
||||
const newSize: any = {};
|
||||
|
||||
if (item.width) newSize.width = item.width;
|
||||
if (item.height) newSize.height = item.height;
|
||||
|
||||
if (direction === 'bottom') {
|
||||
newSize.height = newHeight;
|
||||
} else {
|
||||
newSize.width = newWidth;
|
||||
}
|
||||
|
||||
const newLayout = setLayoutItemProps(props.layout, item.key, newSize);
|
||||
|
||||
props.onResize({ layout: newLayout });
|
||||
eventEmitter.current.emit('resize');
|
||||
}
|
||||
|
||||
function onResizeStop(_event: any, _direction: any, _refToElement: HTMLDivElement, delta: any) {
|
||||
function onResizeStop(_event: any, _direction: any, _refToElement: any, delta: any) {
|
||||
onResize(_event, _direction, _refToElement, delta);
|
||||
setResizedItem(null);
|
||||
}
|
||||
|
||||
if (!item.children) {
|
||||
const size = itemSize(item, parent, sizes, false);
|
||||
|
||||
const comp = props.renderItem(item.key, {
|
||||
item: item,
|
||||
eventEmitter: eventEmitter.current,
|
||||
size: sizes[item.key],
|
||||
size: size,
|
||||
visible: isVisible,
|
||||
});
|
||||
|
||||
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, [comp], isLastChild);
|
||||
const wrapper = renderItemWrapper(comp, item, parent, size, props.moveMode);
|
||||
|
||||
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
|
||||
} else {
|
||||
const childrenComponents = [];
|
||||
for (let i = 0; i < item.children.length; i++) {
|
||||
const child = item.children[i];
|
||||
childrenComponents.push(renderLayoutItem(child, sizes, isVisible && child.visible !== false, i === item.children.length - 1));
|
||||
childrenComponents.push(renderLayoutItem(child, item, sizes, isVisible && itemVisible(child, props.moveMode), i === item.children.length - 1));
|
||||
}
|
||||
|
||||
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild);
|
||||
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
|
||||
}
|
||||
}
|
||||
|
||||
useWindowResizeEvent(eventEmitter);
|
||||
const sizes = useLayoutItemSizes(props.layout);
|
||||
useEffect(() => {
|
||||
validateLayout(props.layout);
|
||||
}, [props.layout]);
|
||||
|
||||
return renderLayoutItem(props.layout, sizes, props.layout.visible !== false, true);
|
||||
useWindowResizeEvent(eventEmitter);
|
||||
const sizes = useLayoutItemSizes(props.layout, props.moveMode);
|
||||
|
||||
function renderMoveModeBox(rootComp: any) {
|
||||
return (
|
||||
<MoveModeRootWrapper>
|
||||
<MoveModeRootMessage>{props.moveModeMessage}</MoveModeRootMessage>
|
||||
{rootComp}
|
||||
</MoveModeRootWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const rootComp = renderLayoutItem(props.layout, null, sizes, itemVisible(props.layout, props.moveMode), true);
|
||||
|
||||
if (props.moveMode) {
|
||||
return renderMoveModeBox(rootComp);
|
||||
} else {
|
||||
return rootComp;
|
||||
}
|
||||
}
|
||||
|
||||
export default ResizableLayout;
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
import { useMemo } from 'react';
|
||||
import { LayoutItem, Size, dragBarThickness } from '../ResizableLayout';
|
||||
|
||||
export interface LayoutItemSizes {
|
||||
[key: string]: Size;
|
||||
}
|
||||
|
||||
export function itemSize(item: LayoutItem, sizes: LayoutItemSizes): Size {
|
||||
return {
|
||||
width: 'width' in item ? item.width : sizes[item.key].width,
|
||||
height: 'height' in item ? item.height : sizes[item.key].height,
|
||||
};
|
||||
}
|
||||
|
||||
function calculateChildrenSizes(item: LayoutItem, sizes: LayoutItemSizes): LayoutItemSizes {
|
||||
if (!item.children) return sizes;
|
||||
|
||||
const parentSize = itemSize(item, sizes);
|
||||
|
||||
const remainingSize: Size = {
|
||||
width: parentSize.width,
|
||||
height: parentSize.height,
|
||||
};
|
||||
|
||||
const noWidthChildren: LayoutItem[] = [];
|
||||
const noHeightChildren: LayoutItem[] = [];
|
||||
|
||||
for (const child of item.children) {
|
||||
let w = 'width' in child ? child.width : null;
|
||||
let h = 'height' in child ? child.height : null;
|
||||
if (child.visible === false) {
|
||||
w = 0;
|
||||
h = 0;
|
||||
}
|
||||
|
||||
if (item.resizableRight) w -= dragBarThickness;
|
||||
if (item.resizableBottom) h -= dragBarThickness;
|
||||
|
||||
sizes[child.key] = { width: w, height: h };
|
||||
if (w !== null) remainingSize.width -= w;
|
||||
if (h !== null) remainingSize.height -= h;
|
||||
if (w === null) noWidthChildren.push(child);
|
||||
if (h === null) noHeightChildren.push(child);
|
||||
}
|
||||
|
||||
if (noWidthChildren.length) {
|
||||
const w = item.direction === 'row' ? remainingSize.width / noWidthChildren.length : parentSize.width;
|
||||
for (const child of noWidthChildren) {
|
||||
sizes[child.key].width = w;
|
||||
}
|
||||
}
|
||||
|
||||
if (noHeightChildren.length) {
|
||||
const h = item.direction === 'column' ? remainingSize.height / noHeightChildren.length : parentSize.height;
|
||||
for (const child of noHeightChildren) {
|
||||
sizes[child.key].height = h;
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of item.children) {
|
||||
const childrenSizes = calculateChildrenSizes(child, sizes);
|
||||
sizes = { ...sizes, ...childrenSizes };
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
export default function useLayoutItemSizes(layout: LayoutItem) {
|
||||
return useMemo(() => {
|
||||
let sizes: LayoutItemSizes = {};
|
||||
|
||||
if (!('width' in layout) || !('height' in layout)) throw new Error('width and height are required on layout root');
|
||||
|
||||
sizes[layout.key] = {
|
||||
width: layout.width,
|
||||
height: layout.height,
|
||||
};
|
||||
|
||||
sizes = calculateChildrenSizes(layout, sizes);
|
||||
|
||||
return sizes;
|
||||
}, [layout]);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { LayoutItem } from './types';
|
||||
|
||||
export default function findItemByKey(layout: LayoutItem, key: string): LayoutItem {
|
||||
if (!layout) throw new Error('Layout cannot be null');
|
||||
|
||||
function recurseFind(item: LayoutItem): LayoutItem {
|
||||
if (item.key === key) return item;
|
||||
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
const found = recurseFind(child);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return recurseFind(layout);
|
||||
|
||||
// const output = recurseFind(layout);
|
||||
// if (!output) throw new Error(`Could not find item "${key}"`);
|
||||
// return output;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { tempContainerPrefix } from './types';
|
||||
|
||||
export default function(itemKey: string): boolean {
|
||||
return itemKey.indexOf(tempContainerPrefix) === 0;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { LayoutItem } from './types';
|
||||
|
||||
type ItemItemCallback = (itemIndex: number, item: LayoutItem, parent: LayoutItem)=> boolean;
|
||||
|
||||
export default function iterateItems(layout: LayoutItem, callback: ItemItemCallback) {
|
||||
const result = callback(0, layout, null);
|
||||
if (result === false) return;
|
||||
|
||||
function recurseFind(item: LayoutItem, callback: Function): boolean {
|
||||
if (item.children) {
|
||||
for (let childIndex = 0; childIndex < item.children.length; childIndex++) {
|
||||
const child = item.children[childIndex];
|
||||
if (callback(childIndex, child, item) === false) return false;
|
||||
if (recurseFind(child, callback) === false) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recurseFind(layout, callback) === false) return;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import findItemByKey from './findItemByKey';
|
||||
import { LayoutItem } from './types';
|
||||
|
||||
export default function layoutItemProp(layout: LayoutItem, key: string, propName: string) {
|
||||
const item = findItemByKey(layout, key);
|
||||
if (!item) throw new Error(`Could not find layout item: ${key}`);
|
||||
return (item as any)[propName];
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
import { LayoutItem, LayoutItemDirection } from './types';
|
||||
import validateLayout from './validateLayout';
|
||||
import { canMove, MoveDirection, moveHorizontal, moveVertical } from './movements';
|
||||
import findItemByKey from './findItemByKey';
|
||||
|
||||
describe('movements', () => {
|
||||
|
||||
test('should move items horizontally to the right', () => {
|
||||
let layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(() => moveHorizontal(layout, 'col1', -1)).toThrow();
|
||||
|
||||
layout = moveHorizontal(layout, 'col1', 1);
|
||||
|
||||
expect(layout.children[0].children[0].key).toBe('col2');
|
||||
expect(layout.children[0].children[1].key).toBe('col1');
|
||||
expect(layout.children[1].key).toBe('col3');
|
||||
|
||||
layout = moveHorizontal(layout, 'col1', 1);
|
||||
|
||||
expect(layout.children[0].key).toBe('col2');
|
||||
expect(layout.children[1].key).toBe('col1');
|
||||
expect(layout.children[2].key).toBe('col3');
|
||||
|
||||
layout = moveHorizontal(layout, 'col1', 1);
|
||||
layout = moveHorizontal(layout, 'col1', 1);
|
||||
|
||||
expect(() => moveHorizontal(layout, 'col1', 1)).toThrow();
|
||||
});
|
||||
|
||||
test('should move items horizontally to the left', () => {
|
||||
let layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
direction: LayoutItemDirection.Column,
|
||||
children: [
|
||||
{ key: 'item1' },
|
||||
{ key: 'item2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
layout = moveHorizontal(layout, 'item2', -1);
|
||||
|
||||
expect(layout.children[0].key).toBe('item2');
|
||||
expect(layout.children[1].key).toBe('item1');
|
||||
expect(layout.children[2].key).toBe('col2');
|
||||
});
|
||||
|
||||
test('should move items vertically', () => {
|
||||
let layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
direction: LayoutItemDirection.Column,
|
||||
children: [
|
||||
{ key: 'row1' },
|
||||
{ key: 'row2' },
|
||||
{ key: 'row3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
layout = moveVertical(layout, 'row3', -1);
|
||||
|
||||
expect(layout.children[1].children[0].key).toBe('row1');
|
||||
expect(layout.children[1].children[1].key).toBe('row3');
|
||||
expect(layout.children[1].children[2].key).toBe('row2');
|
||||
});
|
||||
|
||||
test('should tell if item can be moved', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
resizableRight: true,
|
||||
direction: LayoutItemDirection.Column,
|
||||
children: [
|
||||
{ key: 'row1' },
|
||||
{ key: 'row2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(canMove(MoveDirection.Up, findItemByKey(layout, 'col1'), findItemByKey(layout, 'root'))).toBe(false);
|
||||
expect(canMove(MoveDirection.Down, findItemByKey(layout, 'col1'), findItemByKey(layout, 'root'))).toBe(false);
|
||||
expect(canMove(MoveDirection.Left, findItemByKey(layout, 'col1'), findItemByKey(layout, 'root'))).toBe(false);
|
||||
expect(canMove(MoveDirection.Right, findItemByKey(layout, 'col1'), findItemByKey(layout, 'root'))).toBe(true);
|
||||
|
||||
expect(canMove(MoveDirection.Up, findItemByKey(layout, 'row1'), findItemByKey(layout, 'col1'))).toBe(false);
|
||||
expect(canMove(MoveDirection.Down, findItemByKey(layout, 'row1'), findItemByKey(layout, 'col1'))).toBe(true);
|
||||
expect(canMove(MoveDirection.Left, findItemByKey(layout, 'row1'), findItemByKey(layout, 'col1'))).toBe(true);
|
||||
expect(canMove(MoveDirection.Right, findItemByKey(layout, 'row1'), findItemByKey(layout, 'col1'))).toBe(true);
|
||||
|
||||
expect(canMove(MoveDirection.Up, findItemByKey(layout, 'row2'), findItemByKey(layout, 'col1'))).toBe(true);
|
||||
expect(canMove(MoveDirection.Down, findItemByKey(layout, 'row2'), findItemByKey(layout, 'col1'))).toBe(false);
|
||||
expect(canMove(MoveDirection.Left, findItemByKey(layout, 'row2'), findItemByKey(layout, 'col1'))).toBe(true);
|
||||
expect(canMove(MoveDirection.Right, findItemByKey(layout, 'row2'), findItemByKey(layout, 'col1'))).toBe(true);
|
||||
|
||||
expect(canMove(MoveDirection.Up, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(false);
|
||||
expect(canMove(MoveDirection.Down, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(false);
|
||||
expect(canMove(MoveDirection.Left, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(true);
|
||||
expect(canMove(MoveDirection.Right, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(false);
|
||||
});
|
||||
|
||||
test('Container with only one child should take the width of its parent', () => {
|
||||
let layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
layout = moveHorizontal(layout, 'col2', -1);
|
||||
|
||||
expect(layout.children[0].children[0].key).toBe('col1');
|
||||
expect(layout.children[0].children[0].width).toBe(undefined);
|
||||
});
|
||||
|
||||
test('Temp container should take the width of the child it replaces', () => {
|
||||
let layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
layout = moveHorizontal(layout, 'col2', -1);
|
||||
|
||||
expect(layout.children[0].width).toBe(20);
|
||||
expect(layout.children[0].children[0].width).toBe(undefined);
|
||||
expect(layout.children[0].children[1].width).toBe(undefined);
|
||||
});
|
||||
|
||||
test('Last child should have flexible width if all siblings have fixed width', () => {
|
||||
let layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
layout = moveHorizontal(layout, 'col3', -1);
|
||||
|
||||
expect(layout.children[0].width).toBe(20);
|
||||
expect(layout.children[1].width).toBe(undefined);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,190 @@
|
|||
import iterateItems from './iterateItems';
|
||||
import { LayoutItem, LayoutItemDirection, tempContainerPrefix } from './types';
|
||||
import produce from 'immer';
|
||||
import uuid from '@joplin/lib/uuid';
|
||||
import validateLayout from './validateLayout';
|
||||
|
||||
export enum MoveDirection {
|
||||
Up = 'up',
|
||||
Down = 'down',
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
}
|
||||
|
||||
enum MovementDirection {
|
||||
Horizontal = 1,
|
||||
Vertical = 2,
|
||||
}
|
||||
|
||||
function array_move(arr: any[], old_index: number, new_index: number) {
|
||||
arr = arr.slice();
|
||||
if (new_index >= arr.length) {
|
||||
let k = new_index - arr.length + 1;
|
||||
while (k--) {
|
||||
arr.push(undefined);
|
||||
}
|
||||
}
|
||||
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
||||
return arr;
|
||||
}
|
||||
|
||||
function findItemIndex(siblings: LayoutItem[], key: string) {
|
||||
return siblings.findIndex((value: LayoutItem) => {
|
||||
return value.key === key;
|
||||
});
|
||||
}
|
||||
|
||||
function isHorizontalMove(direction: MoveDirection) {
|
||||
return direction === MoveDirection.Left || direction === MoveDirection.Right;
|
||||
}
|
||||
|
||||
function resetItemSizes(items: LayoutItem[]) {
|
||||
return items.map((item: LayoutItem) => {
|
||||
const newItem = { ...item };
|
||||
delete newItem.width;
|
||||
delete newItem.height;
|
||||
return newItem;
|
||||
});
|
||||
}
|
||||
|
||||
export function canMove(direction: MoveDirection, item: LayoutItem, parent: LayoutItem) {
|
||||
if (!parent) return false;
|
||||
|
||||
if (isHorizontalMove(direction)) {
|
||||
if (parent.isRoot) {
|
||||
const idx = direction === MoveDirection.Left ? 0 : parent.children.length - 1;
|
||||
return parent.children[idx] !== item;
|
||||
} else if (parent.direction === LayoutItemDirection.Column) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (parent.isRoot) {
|
||||
return false;
|
||||
} else if (parent.direction === LayoutItemDirection.Column) {
|
||||
const idx = direction === MoveDirection.Up ? 0 : parent.children.length - 1;
|
||||
return parent.children[idx] !== item;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unhandled case');
|
||||
}
|
||||
|
||||
// For all movements we make the assumption that there's a root container,
|
||||
// which is a row of multiple columns. Within each of these columns there
|
||||
// can be multiple rows (one item per row). Items cannot be more deeply
|
||||
// nested.
|
||||
function moveItem(direction: MovementDirection, layout: LayoutItem, key: string, inc: number): LayoutItem {
|
||||
const itemParents: Record<string, LayoutItem> = {};
|
||||
|
||||
const itemIsRoot = (item: LayoutItem) => {
|
||||
return !itemParents[item.key];
|
||||
};
|
||||
|
||||
const updatedLayout = produce(layout, (draft: any) => {
|
||||
iterateItems(draft, (itemIndex: number, item: LayoutItem, parent: LayoutItem) => {
|
||||
itemParents[item.key] = parent;
|
||||
|
||||
if (item.key !== key || !parent) return true;
|
||||
|
||||
// - "flow" means we are moving an item horizontally within a
|
||||
// row
|
||||
// - "contrary" means we are moving an item horizontally within
|
||||
// a column. Sicen it can't move horizontally, it is moved
|
||||
// out of its container. And vice-versa for vertical
|
||||
// movements.
|
||||
let moveType = null;
|
||||
|
||||
if (direction === MovementDirection.Horizontal && parent.direction === LayoutItemDirection.Row) moveType = 'flow';
|
||||
if (direction === MovementDirection.Horizontal && parent.direction === LayoutItemDirection.Column) moveType = 'contrary';
|
||||
if (direction === MovementDirection.Vertical && parent.direction === LayoutItemDirection.Column) moveType = 'flow';
|
||||
if (direction === MovementDirection.Vertical && parent.direction === LayoutItemDirection.Row) moveType = 'contrary';
|
||||
|
||||
if (moveType === 'flow') {
|
||||
const newIndex = itemIndex + inc;
|
||||
|
||||
if (newIndex >= parent.children.length || newIndex < 0) throw new Error(`Cannot move item "${key}" from position ${itemIndex} to ${newIndex}`);
|
||||
|
||||
// If the item next to it is a container (has children),
|
||||
// move the item inside the container
|
||||
if (parent.children[newIndex].children) {
|
||||
const newParent = parent.children[newIndex];
|
||||
parent.children.splice(itemIndex, 1);
|
||||
newParent.children.push(item);
|
||||
newParent.children = resetItemSizes(newParent.children);
|
||||
} else {
|
||||
// If the item is a child of the root container, create
|
||||
// a new column at `newIndex` and move the item that
|
||||
// was there, as well as the current item, in this
|
||||
// container.
|
||||
if (itemIsRoot(parent)) {
|
||||
const targetChild = parent.children[newIndex];
|
||||
|
||||
// The new container takes the size of the item it
|
||||
// replaces.
|
||||
const newSize: any = {};
|
||||
if (direction === MovementDirection.Horizontal) {
|
||||
if ('width' in targetChild) newSize.width = targetChild.width;
|
||||
} else {
|
||||
if ('height' in targetChild) newSize.height = targetChild.height;
|
||||
}
|
||||
|
||||
const newParent: LayoutItem = {
|
||||
key: `${tempContainerPrefix}${uuid.createNano()}`,
|
||||
direction: LayoutItemDirection.Column,
|
||||
children: [
|
||||
targetChild,
|
||||
item,
|
||||
],
|
||||
...newSize,
|
||||
};
|
||||
|
||||
parent.children[newIndex] = newParent;
|
||||
parent.children.splice(itemIndex, 1);
|
||||
|
||||
newParent.children = resetItemSizes(newParent.children);
|
||||
} else {
|
||||
// Otherwise the default case is simply to move the
|
||||
// item left/right
|
||||
parent.children = array_move(parent.children, itemIndex, newIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const parentParent = itemParents[parent.key];
|
||||
const parentIndex = findItemIndex(parentParent.children, parent.key);
|
||||
|
||||
parent.children.splice(itemIndex, 1);
|
||||
|
||||
let newInc = inc;
|
||||
if (parent.children.length <= 1) {
|
||||
parentParent.children[parentIndex] = parent.children[0];
|
||||
newInc = inc < 0 ? inc + 1 : inc;
|
||||
}
|
||||
|
||||
const newItemIndex = parentIndex + newInc;
|
||||
|
||||
parentParent.children.splice(newItemIndex, 0, item);
|
||||
parentParent.children = resetItemSizes(parentParent.children);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
return validateLayout(updatedLayout);
|
||||
}
|
||||
|
||||
export function moveHorizontal(layout: LayoutItem, key: string, inc: number): LayoutItem {
|
||||
return moveItem(MovementDirection.Horizontal, layout, key, inc);
|
||||
}
|
||||
|
||||
export function moveVertical(layout: LayoutItem, key: string, inc: number): LayoutItem {
|
||||
return moveItem(MovementDirection.Vertical, layout, key, inc);
|
||||
}
|
||||
|
||||
export function move(layout: LayoutItem, key: string, direction: MoveDirection): LayoutItem {
|
||||
if (direction === MoveDirection.Up) return moveVertical(layout, key, -1);
|
||||
if (direction === MoveDirection.Down) return moveVertical(layout, key, +1);
|
||||
if (direction === MoveDirection.Left) return moveHorizontal(layout, key, -1);
|
||||
if (direction === MoveDirection.Right) return moveHorizontal(layout, key, +1);
|
||||
throw new Error('Unreachable');
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import { loadLayout, saveLayout } from './persist';
|
||||
import { LayoutItem, LayoutItemDirection } from './types';
|
||||
import validateLayout from './validateLayout';
|
||||
|
||||
describe('persist', () => {
|
||||
|
||||
test('should save layout and filter out non-user properties', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
direction: LayoutItemDirection.Column,
|
||||
children: [
|
||||
{ key: 'item1', height: 20 },
|
||||
{ key: 'item2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const toSave = saveLayout(layout);
|
||||
|
||||
expect(toSave.key).toBe('root');
|
||||
expect(toSave.width).toBeUndefined();
|
||||
expect(toSave.height).toBeUndefined();
|
||||
expect(toSave.direction).toBeUndefined();
|
||||
expect(toSave.children.length).toBe(3);
|
||||
|
||||
expect(toSave.children[1].key).toBe('col2');
|
||||
expect(toSave.children[1].direction).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should load a layout', () => {
|
||||
const layout: any = {
|
||||
key: 'root',
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
children: [
|
||||
{ key: 'item1', height: 20 },
|
||||
{ key: 'item2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const loaded = loadLayout(layout, null, { width: 100, height: 200 });
|
||||
|
||||
expect(loaded.key).toBe('root');
|
||||
expect(loaded.width).toBe(100);
|
||||
expect(loaded.height).toBe(200);
|
||||
expect(loaded.direction).toBe(LayoutItemDirection.Row);
|
||||
expect(loaded.children.length).toBe(3);
|
||||
|
||||
expect(loaded.children[1].key).toBe('col2');
|
||||
expect(loaded.children[1].direction).toBe(LayoutItemDirection.Column);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
import { LayoutItem, Size } from './types';
|
||||
import produce from 'immer';
|
||||
import iterateItems from './iterateItems';
|
||||
import validateLayout from './validateLayout';
|
||||
|
||||
export function saveLayout(layout: LayoutItem): any {
|
||||
const propertyWhiteList = [
|
||||
'visible',
|
||||
'width',
|
||||
'height',
|
||||
'children',
|
||||
'key',
|
||||
'context',
|
||||
];
|
||||
|
||||
return produce(layout, (draft: any) => {
|
||||
delete draft.width;
|
||||
delete draft.height;
|
||||
iterateItems(draft, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
||||
for (const k of Object.keys(item)) {
|
||||
if (!propertyWhiteList.includes(k)) delete (item as any)[k];
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function loadLayout(layout: any, defaultLayout: LayoutItem, rootSize: Size): LayoutItem {
|
||||
let output: LayoutItem = null;
|
||||
|
||||
if (layout) {
|
||||
output = { ...layout };
|
||||
} else {
|
||||
output = { ...defaultLayout };
|
||||
}
|
||||
|
||||
output.width = rootSize.width;
|
||||
output.height = rootSize.height;
|
||||
|
||||
return validateLayout(output);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import produce from 'immer';
|
||||
import iterateItems from './iterateItems';
|
||||
import { LayoutItem } from './types';
|
||||
import validateLayout from './validateLayout';
|
||||
|
||||
export default function(layout: LayoutItem, itemKey: string): LayoutItem {
|
||||
const output = produce(layout, (layoutDraft: LayoutItem) => {
|
||||
iterateItems(layoutDraft, (itemIndex: number, item: LayoutItem, parent: LayoutItem) => {
|
||||
if (item.key === itemKey) {
|
||||
parent.children.splice(itemIndex, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return output !== layout ? validateLayout(output) : layout;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import produce from 'immer';
|
||||
import { LayoutItem } from './types';
|
||||
import validateLayout from './validateLayout';
|
||||
|
||||
export default function setLayoutItemProps(layout: LayoutItem, key: string, props: any) {
|
||||
return validateLayout(produce(layout, (draftState: LayoutItem) => {
|
||||
function recurseFind(item: LayoutItem) {
|
||||
if (item.key === key) {
|
||||
for (const n in props) {
|
||||
(item as any)[n] = props[n];
|
||||
}
|
||||
} else {
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
recurseFind(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurseFind(draftState);
|
||||
}));
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { ThemeAppearance } from '@joplin/lib/themes/type';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StyledWrapperRoot = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: ${props => props.size.width}px;
|
||||
height: ${props => props.size.height}px;
|
||||
`;
|
||||
|
||||
export const StyledMoveOverlay = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
background-color: ${props => props.theme.appearance === ThemeAppearance.Light ? 'rgba(0,0,0,0.5)' : 'rgba(255,255,255,0.5)'};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const MoveModeRootWrapper = styled.div`
|
||||
position:relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const MoveModeRootMessage = styled.div`
|
||||
position:absolute;
|
||||
bottom: 10px;
|
||||
z-index:200;
|
||||
background-color: ${props => props.theme.backgroundColor};
|
||||
padding: 10px;
|
||||
border-radius: 5;
|
||||
`;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue