diff --git a/package-lock.json b/package-lock.json index cd4f17796..42749f886 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "trilium", - "version": "0.40.5", + "version": "0.40.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2079,9 +2079,9 @@ "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" }, "dayjs": { - "version": "1.8.22", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.22.tgz", - "integrity": "sha512-N8IXfxBD62Y9cKTuuuSoOlCXRnnzaTj1vu91r855iq6FbY5cZqOZnW/95nUn6kJiR+W9PHHrLykEoQOe6fUKxQ==" + "version": "1.8.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.23.tgz", + "integrity": "sha512-NmYHMFONftoZbeOhVz6jfiXI4zSiPN6NoVWJgC0aZQfYVwzy/ZpESPHuCcI0B8BUMpSJQ08zenHDbofOLKq8hQ==" }, "debug": { "version": "4.1.1", @@ -2590,9 +2590,9 @@ "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" }, "electron": { - "version": "9.0.0-beta.7", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.7.tgz", - "integrity": "sha512-IHWBLDUjlctyzSY1cXT9b2xdUFACiwWXV38WSkIbW4Ykd2fsNxn2ESRK/JUSby1oQZL6z4fUQRUOrg/gzuoV4Q==", + "version": "9.0.0-beta.9", + "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.9.tgz", + "integrity": "sha512-jVt+UL22S+uRiHkt2piAoVduL/n7UCTRKbp0c42kq1+Q36BVDu4j2HrePGX1Zilmnk91j8a6AqQ/x0dv6LkDnA==", "dev": true, "requires": { "@electron/get": "^1.0.1", diff --git a/package.json b/package.json index 41cba63c3..1e5716176 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "ws": "7.2.3" }, "devDependencies": { - "electron": "9.0.0-beta.7", + "electron": "9.0.0-beta.9", "electron-builder": "22.4.1", "electron-packager": "14.2.1", "electron-rebuild": "1.10.1", diff --git a/src/public/javascripts/desktop.js b/src/public/javascripts/desktop.js index 79140ec13..91a6c9016 100644 --- a/src/public/javascripts/desktop.js +++ b/src/public/javascripts/desktop.js @@ -67,6 +67,7 @@ import ProtectedSessionTypeWidget from "./widgets/type_widgets/protected_session import BookTypeWidget from "./widgets/type_widgets/book.js"; import contextMenu from "./services/context_menu.js"; import DesktopLayout from "./widgets/desktop_layout.js"; +import bundleService from "./services/bundle.js"; if (utils.isElectron()) { require('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) { @@ -80,8 +81,12 @@ $('[data-toggle="tooltip"]').tooltip({ macInit.init(); -appContext.setLayout(new DesktopLayout()); -appContext.start(); +bundleService.getWidgetBundlesByParent().then(widgetBundles => { + const desktopLayout = new DesktopLayout(widgetBundles); + + appContext.setLayout(desktopLayout); + appContext.start(); +}); noteTooltipService.setupGlobalTooltip(); diff --git a/src/public/javascripts/services/app_context.js b/src/public/javascripts/services/app_context.js index 0e71bc20b..5eaeb56df 100644 --- a/src/public/javascripts/services/app_context.js +++ b/src/public/javascripts/services/app_context.js @@ -1,4 +1,3 @@ -import server from "./server.js"; import treeCache from "./tree_cache.js"; import bundleService from "./bundle.js"; import DialogCommandExecutor from "./dialog_command_executor.js"; @@ -18,10 +17,10 @@ class AppContext extends Component { } async start() { - options.load(await server.get('options')); - this.showWidgets(); + await Promise.all([treeCache.initializedPromise, options.initializedPromise]); + this.tabManager.loadTabs(); setTimeout(() => bundleService.executeStartupBundles(), 2000); diff --git a/src/public/javascripts/services/bundle.js b/src/public/javascripts/services/bundle.js index 33ee8ddee..cbb5635d3 100644 --- a/src/public/javascripts/services/bundle.js +++ b/src/public/javascripts/services/bundle.js @@ -1,6 +1,7 @@ import ScriptContext from "./script_context.js"; import server from "./server.js"; import toastService from "./toast.js"; +import treeCache from "./tree_cache.js"; async function getAndExecuteBundle(noteId, originEntity = null) { const bundle = await server.get('script/bundle/' + noteId); @@ -29,8 +30,40 @@ async function executeStartupBundles() { } } +async function getWidgetBundlesByParent() { + const scriptBundles = await server.get("script/widgets"); + + const byParent = {}; + + for (const bundle of scriptBundles) { + + let widget; + + try { + widget = await executeBundle(bundle); + } + catch (e) { + console.error("Widget initialization failed: ", e); + continue; + } + + if (!widget.getParentWidget) { + console.log(`Custom widget does not have mandatory 'getParent()' method defined`); + continue; + } + + const parentWidgetName = widget.getParentWidget(); + + byParent[parentWidgetName] = byParent[parentWidgetName] || []; + byParent[parentWidgetName].push(widget); + } + + return byParent; +} + export default { executeBundle, getAndExecuteBundle, - executeStartupBundles + executeStartupBundles, + getWidgetBundlesByParent } \ No newline at end of file diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js index 54ebe72d1..8e1ca4d54 100644 --- a/src/public/javascripts/services/frontend_script_api.js +++ b/src/public/javascripts/services/frontend_script_api.js @@ -282,7 +282,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain * @method * @returns {NoteShort} active note (loaded into right pane) */ - this.getActiveTabNote = appContext.tabManager.getActiveTabNote; + this.getActiveTabNote = () => appContext.tabManager.getActiveTabNote(); /** * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance. @@ -296,7 +296,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain * @method * @returns {Promise} returns note path of active note or null if there isn't active note */ - this.getActiveTabNotePath = appContext.tabManager.getActiveTabNotePath; + this.getActiveTabNotePath = () => appContext.tabManager.getActiveTabNotePath(); /** * @method diff --git a/src/public/javascripts/services/options.js b/src/public/javascripts/services/options.js index a92b8151e..4d4c1213e 100644 --- a/src/public/javascripts/services/options.js +++ b/src/public/javascripts/services/options.js @@ -1,8 +1,10 @@ import server from "./server.js"; -const loadListeners = []; - class Options { + constructor() { + this.initializedPromise = server.get('options').then(data => this.load(data)); + } + load(arr) { this.arr = arr; } diff --git a/src/public/javascripts/services/tab_manager.js b/src/public/javascripts/services/tab_manager.js index ba5d0fdeb..683f64d93 100644 --- a/src/public/javascripts/services/tab_manager.js +++ b/src/public/javascripts/services/tab_manager.js @@ -32,8 +32,6 @@ export default class TabManager extends Component { async loadTabs() { const openTabs = options.getJson('openTabs') || []; - await treeCache.initializedPromise; - // if there's notePath in the URL, make sure it's open and active // (useful, among others, for opening clipped notes from clipper) if (window.location.hash) { diff --git a/src/public/javascripts/widgets/basic_widget.js b/src/public/javascripts/widgets/basic_widget.js index be7a92afe..35ff2c203 100644 --- a/src/public/javascripts/widgets/basic_widget.js +++ b/src/public/javascripts/widgets/basic_widget.js @@ -9,6 +9,8 @@ class BasicWidget extends Component { style: '' }; this.classes = []; + + this.position = 0; } id(id) { @@ -87,6 +89,10 @@ class BasicWidget extends Component { return this.$widget.is(":visible"); } + getPosition() { + return this.position; + } + remove() { if (this.$widget) { this.$widget.remove(); diff --git a/src/public/javascripts/widgets/component.js b/src/public/javascripts/widgets/component.js index f951ce8c1..42bb2ca8f 100644 --- a/src/public/javascripts/widgets/component.js +++ b/src/public/javascripts/widgets/component.js @@ -36,6 +36,14 @@ export default class Component { return this; } + addChildren(components = []) { + for (const component of components) { + this.child(component); + } + + return this; + } + /** @return {Promise} */ handleEvent(name, data) { return Promise.all([ diff --git a/src/public/javascripts/widgets/desktop_layout.js b/src/public/javascripts/widgets/desktop_layout.js index 7eb67cfdf..e05f33f97 100644 --- a/src/public/javascripts/widgets/desktop_layout.js +++ b/src/public/javascripts/widgets/desktop_layout.js @@ -99,6 +99,10 @@ const RIGHT_PANE_CSS = ` `; export default class DesktopLayout { + constructor(customWidgets) { + this.customWidgets = customWidgets; + } + getRootWidget(appContext) { appContext.mainTreeWidget = new NoteTreeWidget(); @@ -144,6 +148,7 @@ export default class DesktopLayout { .child(new TabCachingWidget(() => new NoteRevisionsWidget())) .child(new TabCachingWidget(() => new SimilarNotesWidget())) .child(new TabCachingWidget(() => new WhatLinksHereWidget())) + .addChildren(this.customWidgets['right-pane']) ) .child(new SidePaneToggles().hideInZenMode()) ); diff --git a/src/public/javascripts/widgets/flex_container.js b/src/public/javascripts/widgets/flex_container.js index c768caea3..b52d2daaf 100644 --- a/src/public/javascripts/widgets/flex_container.js +++ b/src/public/javascripts/widgets/flex_container.js @@ -11,6 +11,17 @@ export default class FlexContainer extends BasicWidget { this.attrs.style = `display: flex; flex-direction: ${direction};`; this.children = []; + + this.positionCounter = 10; + } + + child(component) { + super.child(component); + + component.position = this.positionCounter; + this.positionCounter += 10; + + return this; } doRender() { diff --git a/src/routes/api/script.js b/src/routes/api/script.js index a2792898f..cd827f5b8 100644 --- a/src/routes/api/script.js +++ b/src/routes/api/script.js @@ -29,8 +29,8 @@ async function run(req) { return { executionResult: result }; } -async function getStartupBundles() { - const notes = await attributeService.getNotesWithLabel("run", "frontendStartup"); +async function getBundlesWithLabel(label, value) { + const notes = await attributeService.getNotesWithLabel(label, value); const bundles = []; @@ -45,6 +45,14 @@ async function getStartupBundles() { return bundles; } +async function getStartupBundles() { + return await getBundlesWithLabel("run", "frontendStartup"); +} + +async function getWidgetBundles() { + return await getBundlesWithLabel("widget"); +} + async function getRelationBundles(req) { const noteId = req.params.noteId; const note = await repository.getNote(noteId); @@ -84,6 +92,7 @@ module.exports = { exec, run, getStartupBundles, + getWidgetBundles, getRelationBundles, getBundle }; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index cb580b32d..e61948e63 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -225,6 +225,7 @@ function register(app) { apiRoute(POST, '/api/script/exec', scriptRoute.exec); apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run); apiRoute(GET, '/api/script/startup', scriptRoute.getStartupBundles); + apiRoute(GET, '/api/script/widgets', scriptRoute.getWidgetBundles); apiRoute(GET, '/api/script/bundle/:noteId', scriptRoute.getBundle); apiRoute(GET, '/api/script/relation/:noteId/:relationName', scriptRoute.getRelationBundles);