shortcuts improvements

This commit is contained in:
zadam 2022-11-27 23:43:25 +01:00
parent e4f57ab2fe
commit a459230aa9
10 changed files with 168 additions and 174 deletions

View File

@ -1,3 +1,9 @@
<p>Please define the target script note in the promoted attributes. This script will be executed immediately upon clicking the launchbar icon.</p>
<p>Launchbar displays the title / icon from the shortcut which does not necessarily mirrors those of the target script note.</p>
<h4>Example script</h4>
<pre>
alert("Current note is " + api.getActiveContextNote().title);
</pre>

View File

@ -1 +1,34 @@
<p>Please define the target widget note in the promoted attributes. The widget will be used to render the launchbar icon.</p>
<h4>Example launchbar widget</h4>
<pre>
const TPL = `&lt;div style="height: 53px; width: 53px;"&gt;&lt;/div&gt;`;
class ExampleLaunchbarWidget extends api.NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
}
async refreshWithNote(note) {
this.$widget.css("background-color", this.stringToColor(note.title));
}
stringToColor(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xFF;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}
}
module.exports = new ExampleLaunchbarWidget();
</pre>

View File

@ -792,10 +792,10 @@ class NoteShort {
if (env === "frontend") {
const bundleService = (await import("../services/bundle.js")).default;
await bundleService.getAndExecuteBundle(this.noteId);
return await bundleService.getAndExecuteBundle(this.noteId);
}
else if (env === "backend") {
await server.post('script/run/' + this.noteId);
return await server.post('script/run/' + this.noteId);
}
else {
throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`);

View File

@ -11,7 +11,6 @@ import CollapsibleWidget from '../widgets/collapsible_widget.js';
import ws from "./ws.js";
import appContext from "./app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import NoteContextCachingWidget from "../widgets/note_context_caching_widget.js";
import BasicWidget from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js";
@ -40,24 +39,9 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
/** @property {CollapsibleWidget} */
this.CollapsibleWidget = CollapsibleWidget;
/**
* @property {NoteContextAwareWidget}
* @deprecated use NoteContextAwareWidget instead
*/
this.TabAwareWidget = NoteContextAwareWidget;
/** @property {NoteContextAwareWidget} */
this.NoteContextAwareWidget = NoteContextAwareWidget;
/**
* @property {NoteContextCachingWidget}
* @deprecated use NoteContextCachingWidget instead
*/
this.TabCachingWidget = NoteContextCachingWidget;
/** @property {NoteContextAwareWidget} */
this.NoteContextCachingWidget = NoteContextCachingWidget;
/** @property {BasicWidget} */
this.BasicWidget = BasicWidget;

View File

@ -42,16 +42,23 @@ export default class Component {
/** @returns {Promise} */
handleEvent(name, data) {
const callMethodPromise = this.initialized
? this.initialized.then(() => this.callMethod(this[name + 'Event'], data))
: this.callMethod(this[name + 'Event'], data);
try {
const callMethodPromise = this.initialized
? this.initialized.then(() => this.callMethod(this[name + 'Event'], data))
: this.callMethod(this[name + 'Event'], data);
const childrenPromise = this.handleEventInChildren(name, data);
const childrenPromise = this.handleEventInChildren(name, data);
// don't create promises if not needed (optimization)
return callMethodPromise && childrenPromise
? Promise.all([callMethodPromise, childrenPromise])
: (callMethodPromise || childrenPromise);
// don't create promises if not needed (optimization)
return callMethodPromise && childrenPromise
? Promise.all([callMethodPromise, childrenPromise])
: (callMethodPromise || childrenPromise);
}
catch (e) {
console.error(`Handling of event '${name}' failed in ${this.constructor.name} with error ${e.message} ${e.stack}`);
return null;
}
}
/** @returns {Promise} */

View File

@ -33,53 +33,93 @@ export default class ShortcutContainer extends FlexContainer {
}
for (const shortcut of await visibleShortcutsRoot.getChildNotes()) {
if (shortcut.getLabelValue("command")) {
this.child(new ButtonWidget()
.title(shortcut.title)
.icon(shortcut.getIcon())
.command(shortcut.getLabelValue("command")));
} else if (shortcut.hasRelation('targetNote')) {
this.child(new ButtonWidget()
.title(shortcut.title)
.icon(shortcut.getIcon())
.onClick(() => appContext.tabManager.openTabWithNoteWithHoisting(shortcut.getRelationValue('targetNote'), true)));
} else {
const builtinWidget = shortcut.getLabelValue("builtinWidget");
if (builtinWidget) {
if (builtinWidget === 'calendar') {
this.child(new CalendarWidget(shortcut.title, shortcut.getIcon()));
} else if (builtinWidget === 'spacer') {
// || has to be inside since 0 is a valid value
const baseSize = parseInt(shortcut.getLabelValue("baseSize") || "40");
const growthFactor = parseInt(shortcut.getLabelValue("growthFactor") || "100");
this.child(new SpacerWidget(baseSize, growthFactor));
} else if (builtinWidget === 'pluginButtons') {
this.child(new FlexContainer("column")
.id("plugin-buttons")
.contentSized());
} else if (builtinWidget === 'bookmarks') {
this.child(new BookmarkButtons());
} else if (builtinWidget === 'protectedSession') {
this.child(new ProtectedSessionStatusWidget());
} else if (builtinWidget === 'syncStatus') {
this.child(new SyncStatusWidget());
} else if (builtinWidget === 'backInHistoryButton') {
this.child(new BackInHistoryButtonWidget());
} else if (builtinWidget === 'forwardInHistoryButton') {
this.child(new ForwardInHistoryButtonWidget());
} else {
console.log(`Unrecognized builtin widget ${builtinWidget} for shortcut ${shortcut.noteId} "${shortcut.title}"`);
}
}
try {
await this.initShortcut(shortcut);
}
catch (e) {
console.error(`Initialization of shortcut '${shortcut.noteId}' with title '${shortcut.title}' failed with error: ${e.message} ${e.stack}`);
continue;
}
}
this.$widget.empty();
this.renderChildren();
this.handleEventInChildren('initialRenderComplete');
await this.handleEventInChildren('initialRenderComplete');
const activeContext = appContext.tabManager.getActiveContext();
await this.handleEvent('setNoteContext', {
noteContext: activeContext
});
await this.handleEvent('noteSwitched', {
noteContext: activeContext,
notePath: activeContext.notePath
});
}
async initShortcut(shortcut) {
if (shortcut.type !== 'shortcut') {
console.warn(`Note ${shortcut.noteId} is not a shortcut even though it's in shortcut subtree`);
return;
}
if (shortcut.getLabelValue("command")) {
this.child(new ButtonWidget()
.title(shortcut.title)
.icon(shortcut.getIcon())
.command(shortcut.getLabelValue("command")));
} else if (shortcut.hasRelation('targetNote')) {
this.child(new ButtonWidget()
.title(shortcut.title)
.icon(shortcut.getIcon())
.onClick(() => appContext.tabManager.openTabWithNoteWithHoisting(shortcut.getRelationValue('targetNote'), true)));
} else if (shortcut.hasRelation('script')) {
this.child(new ButtonWidget()
.title(shortcut.title)
.icon(shortcut.getIcon())
.onClick(async () => {
const script = await shortcut.getRelationTarget('script');
await script.executeScript();
}));
} else if (shortcut.hasRelation('widget')) {
const widget = await shortcut.getRelationTarget('widget');
const res = await widget.executeScript();
this.child(res);
} else {
const builtinWidget = shortcut.getLabelValue("builtinWidget");
if (builtinWidget) {
if (builtinWidget === 'calendar') {
this.child(new CalendarWidget(shortcut.title, shortcut.getIcon()));
} else if (builtinWidget === 'spacer') {
// || has to be inside since 0 is a valid value
const baseSize = parseInt(shortcut.getLabelValue("baseSize") || "40");
const growthFactor = parseInt(shortcut.getLabelValue("growthFactor") || "100");
this.child(new SpacerWidget(baseSize, growthFactor));
} else if (builtinWidget === 'pluginButtons') {
this.child(new FlexContainer("column")
.id("plugin-buttons")
.contentSized());
} else if (builtinWidget === 'bookmarks') {
this.child(new BookmarkButtons());
} else if (builtinWidget === 'protectedSession') {
this.child(new ProtectedSessionStatusWidget());
} else if (builtinWidget === 'syncStatus') {
this.child(new SyncStatusWidget());
} else if (builtinWidget === 'backInHistoryButton') {
this.child(new BackInHistoryButtonWidget());
} else if (builtinWidget === 'forwardInHistoryButton') {
this.child(new ForwardInHistoryButtonWidget());
} else {
console.log(`Unrecognized builtin widget ${builtinWidget} for shortcut ${shortcut.noteId} "${shortcut.title}"`);
}
}
}
}
entitiesReloadedEvent({loadResults}) {

View File

@ -1,99 +0,0 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import keyboardActionsService from "../services/keyboard_actions.js";
export default class NoteContextCachingWidget extends NoteContextAwareWidget {
constructor(widgetFactory) {
super();
this.widgetFactory = widgetFactory;
this.widgets = {};
}
doRender() {
return this.$widget = $(`<div class="marker" style="display: none;">`);
}
async newNoteContextCreatedEvent({noteContext}) {
const {ntxId} = noteContext;
if (this.widgets[ntxId]) {
return;
}
this.widgets[ntxId] = this.widgetFactory();
const $renderedWidget = this.widgets[ntxId].render();
this.widgets[ntxId].toggleExt(false); // new tab is always not active, can be activated after creation
this.$widget.after($renderedWidget);
this.widgets[ntxId].handleEvent('initialRenderComplete');
keyboardActionsService.updateDisplayedShortcuts($renderedWidget);
await this.widgets[ntxId].handleEvent('setNoteContext', {noteContext});
this.child(this.widgets[ntxId]); // add as child only once it is ready (rendered with noteContext)
}
noteContextRemovedEvent({ntxIds}) {
for (const ntxId of ntxIds) {
const widget = this.widgets[ntxId];
if (widget) {
widget.remove();
delete this.widgets[ntxId];
this.children = this.children.filter(ch => ch !== widget);
}
}
}
async refresh() {
this.toggleExt(true);
}
toggleInt(show) {} // not needed
toggleExt(show) {
for (const ntxId in this.widgets) {
this.widgets[ntxId].toggleExt(show && this.isNoteContext(ntxId));
}
}
/**
* widget.hasBeenAlreadyShown is intended for lazy loading of cached tabs - initial note switches of new tabs
* are not executed, we're waiting for the first tab activation and then we update the tab. After this initial
* activation further note switches are always propagated to the tabs.
*/
handleEventInChildren(name, data) {
if (['noteSwitched', 'noteSwitchedAndActivated'].includes(name)) {
// this event is propagated only to the widgets of a particular tab
const widget = this.widgets[data.noteContext.ntxId];
if (widget && (widget.hasBeenAlreadyShown || name === 'noteSwitchedAndActivated')) {
widget.hasBeenAlreadyShown = true;
return widget.handleEvent('noteSwitched', data);
}
else {
return Promise.resolve();
}
}
if (name === 'activeContextChanged') {
const widget = this.widgets[data.noteContext.ntxId];
if (widget.hasBeenAlreadyShown) {
return Promise.resolve();
}
else {
widget.hasBeenAlreadyShown = true;
return widget.handleEvent(name, data);
}
} else {
return super.handleEventInChildren(name, data);
}
}
}

View File

@ -422,8 +422,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return true; // allow dragging to start
},
dragEnter: (node, data) => {
console.log(data, node.data.noteType);
if (node.data.noteType === 'search') {
return false;
} else if (node.data.noteId === 'lb_root') {
@ -565,7 +563,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
$span.append($refreshSearchButton);
}
if (note.type !== 'search') {
if (!['search', 'shortcut'].includes(note.type)) {
const $createChildNoteButton = $('<span class="tree-item-button add-note-button bx bx-plus" title="Create child note"></span>');
$span.append($createChildNoteButton);

View File

@ -5,6 +5,13 @@ const TPL = `<div class="note-detail-doc note-detail-printable">
.note-detail-doc-content {
padding: 15px;
}
.note-detail-doc-content pre {
background-color: var(--accented-background-color);
border: 1px solid var(--main-border-color);
padding: 15px;
border-radius: 5px;
}
</style>
<div class="note-detail-doc-content"></div>

View File

@ -103,6 +103,28 @@ function getNewNoteTitle(parentNote) {
return title;
}
function getAndValidateParent(params) {
const parentNote = becca.notes[params.parentNoteId];
if (!parentNote) {
throw new Error(`Parent note "${params.parentNoteId}" not found.`);
}
if (parentNote.type === 'shortcut') {
throw new Error(`Shortcuts should not have child notes.`);
}
if (['hidden', 'lb_root'].includes(parentNote.noteId)) {
throw new Error(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
}
if (['lb_availableshortcuts', 'lb_visibleshortcuts'].includes(parentNote.noteId) && params.type !== 'shortcut') {
throw new Error(`Creating child notes into '${parentNote.noteId}' is only possible for type 'shortcut'.`);
}
return parentNote;
}
/**
* Following object properties are mandatory:
* - {string} parentNoteId
@ -121,11 +143,7 @@ function getNewNoteTitle(parentNote) {
* @return {{note: Note, branch: Branch}}
*/
function createNewNote(params) {
const parentNote = becca.notes[params.parentNoteId];
if (!parentNote) {
throw new Error(`Parent note "${params.parentNoteId}" not found.`);
}
const parentNote = getAndValidateParent(params);
if (params.title === null || params.title === undefined) {
params.title = getNewNoteTitle(parentNote);