basic support for custom widgets

This commit is contained in:
zadam 2020-03-16 21:16:09 +01:00
parent 8ae78a9e23
commit 173030e02e
14 changed files with 99 additions and 22 deletions

14
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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();

View File

@ -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);

View File

@ -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
}

View File

@ -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<string|null>} 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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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();

View File

@ -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([

View File

@ -99,6 +99,10 @@ const RIGHT_PANE_CSS = `
</style>`;
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())
);

View File

@ -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() {

View File

@ -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
};

View File

@ -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);