Merge pull request #1124 from TriliumNext/feature/in_app_help
In-app help
@ -8,7 +8,7 @@ test("Help popup", async ({ page, context }) => {
|
||||
await app.goto();
|
||||
|
||||
const popupPromise = page.waitForEvent("popup");
|
||||
await app.currentNoteSplit.press("F1");
|
||||
await app.currentNoteSplit.press("Shift+F1");
|
||||
await page.getByRole("link", { name: "online" }).click();
|
||||
const popup = await popupPromise;
|
||||
expect(popup.url()).toBe("https://triliumnext.github.io/Docs/");
|
||||
|
@ -24,6 +24,7 @@ import type { Attribute } from "../services/attribute_parser.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
|
||||
import type { ContextMenuEvent } from "../menus/context_menu.js";
|
||||
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||
|
||||
interface Layout {
|
||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
||||
@ -61,8 +62,8 @@ export interface NoteCommandData extends CommandData {
|
||||
viewScope?: ViewScope;
|
||||
}
|
||||
|
||||
export interface ExecuteCommandData extends CommandData {
|
||||
resolve: unknown;
|
||||
export interface ExecuteCommandData<T> extends CommandData {
|
||||
resolve: (data: T) => void
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,6 +78,7 @@ export type CommandMappings = {
|
||||
searchString?: string;
|
||||
ancestorNoteId?: string | null;
|
||||
};
|
||||
closeTocCommand: CommandData;
|
||||
showLaunchBarSubtree: CommandData;
|
||||
showOptions: CommandData & {
|
||||
section: string;
|
||||
@ -151,12 +153,16 @@ export type CommandMappings = {
|
||||
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
|
||||
};
|
||||
executeWithTextEditor: CommandData &
|
||||
ExecuteCommandData & {
|
||||
ExecuteCommandData<TextEditor> & {
|
||||
callback?: GetTextEditorCallback;
|
||||
};
|
||||
executeWithCodeEditor: CommandData & ExecuteCommandData;
|
||||
executeWithContentElement: CommandData & ExecuteCommandData;
|
||||
executeWithTypeWidget: CommandData & ExecuteCommandData;
|
||||
executeWithCodeEditor: CommandData & ExecuteCommandData<null>;
|
||||
/**
|
||||
* Called upon when attempting to retrieve the content element of a {@link NoteContext}.
|
||||
* Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}.
|
||||
*/
|
||||
executeWithContentElement: CommandData & ExecuteCommandData<JQuery<HTMLElement>>;
|
||||
executeWithTypeWidget: CommandData & ExecuteCommandData<TypeWidget | null>;
|
||||
addTextToActiveEditor: CommandData & {
|
||||
text: string;
|
||||
};
|
||||
@ -202,6 +208,9 @@ export type CommandMappings = {
|
||||
zoomFactor: string;
|
||||
}
|
||||
|
||||
reEvaluateRightPaneVisibility: CommandData;
|
||||
runActiveNote: CommandData;
|
||||
|
||||
// Geomap
|
||||
deleteFromMap: { noteId: string },
|
||||
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
|
||||
@ -247,7 +256,7 @@ type EventMappings = {
|
||||
};
|
||||
noteSwitched: {
|
||||
noteContext: NoteContext;
|
||||
notePath: string | null;
|
||||
notePath?: string | null;
|
||||
};
|
||||
noteSwitchedAndActivatedEvent: {
|
||||
noteContext: NoteContext;
|
||||
@ -262,6 +271,9 @@ type EventMappings = {
|
||||
reEvaluateHighlightsListWidgetVisibility: {
|
||||
noteId: string | undefined;
|
||||
};
|
||||
reEvaluateTocWidgetVisibility: {
|
||||
noteId: string | undefined;
|
||||
};
|
||||
showHighlightsListWidget: {
|
||||
noteId: string;
|
||||
};
|
||||
@ -297,7 +309,12 @@ type EventMappings = {
|
||||
};
|
||||
refreshNoteList: {
|
||||
noteId: string;
|
||||
}
|
||||
};
|
||||
showToc: {
|
||||
noteId: string;
|
||||
};
|
||||
scrollToEnd: { ntxId: string };
|
||||
noteTypeMimeChanged: { noteId: string };
|
||||
};
|
||||
|
||||
export type EventListener<T extends EventNames> = {
|
||||
|
@ -9,6 +9,7 @@ import hoistedNoteService from "../services/hoisted_note.js";
|
||||
import options from "../services/options.js";
|
||||
import type { ViewScope } from "../services/link.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||
|
||||
interface SetNoteOpts {
|
||||
triggerSwitchEvent?: unknown;
|
||||
@ -288,7 +289,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
hasNoteList() {
|
||||
return (
|
||||
this.note &&
|
||||
this.viewScope?.viewMode === "default" &&
|
||||
["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "") &&
|
||||
this.note.hasChildren() &&
|
||||
["book", "text", "code"].includes(this.note.type) &&
|
||||
this.note.mime !== "text/x-sqlite;schema=trilium" &&
|
||||
@ -319,6 +320,15 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise which will retrieve the JQuery element of the content of this note context.
|
||||
*
|
||||
* Do note that retrieving the content element needs to be handled by the type widget, which is the one which
|
||||
* provides the content element by listening to the `executeWithContentElement` event. Not all note types support
|
||||
* this.
|
||||
*
|
||||
* If no content could be determined `null` is returned instead.
|
||||
*/
|
||||
async getContentElement() {
|
||||
return this.timeout<JQuery<HTMLElement>>(
|
||||
new Promise((resolve) =>
|
||||
@ -332,7 +342,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
|
||||
async getTypeWidget() {
|
||||
return this.timeout(
|
||||
new Promise((resolve) =>
|
||||
new Promise<TypeWidget | null>((resolve) =>
|
||||
appContext.triggerCommand("executeWithTypeWidget", {
|
||||
resolve,
|
||||
ntxId: this.ntxId
|
||||
|
@ -90,6 +90,10 @@ export default class RootCommandExecutor extends Component {
|
||||
await appContext.tabManager.openTabWithNoteWithHoisting("_backendLog", { activate: true });
|
||||
}
|
||||
|
||||
async showHelpCommand() {
|
||||
await this.showAndHoistSubtree("_help");
|
||||
}
|
||||
|
||||
async showLaunchBarSubtreeCommand() {
|
||||
const rootNote = utils.isMobile() ? "_lbMobileRoot" : "_lbRoot";
|
||||
await this.showAndHoistSubtree(rootNote);
|
||||
|
500
src/public/app/doc_notes/en/User Guide/!!!meta.json
Normal file
@ -0,0 +1,500 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.91.5",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "OkOZllzB3fqN",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN"
|
||||
],
|
||||
"title": "User Guide",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-help-circle",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "User Guide",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "wmegHv51MJMd",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"wmegHv51MJMd"
|
||||
],
|
||||
"title": "Types of notes",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "Types of notes",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "foPEtsL51pD2",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"wmegHv51MJMd",
|
||||
"foPEtsL51pD2"
|
||||
],
|
||||
"title": "Geo map",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-map-alt",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Geo map.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "viN50n5G4kB0",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "eUrcqc8RRuZG",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "1quk4yxJpeHZ",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "mgwGrtQZjxxb",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "JULizn130rVI",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "kcYjOvJDFkbS",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "5_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "ut6vm2aXVfXI",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "6_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "0AwaQMqt3FVA",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "7_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gFR2Izzp18LQ",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "8_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "PMqmCbNLlZOG",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "9_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "pKdtiq4r0eFY",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "10_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "FXRVvYpOxWyR",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "11_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "42AncDs7SSAf",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "12_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gR2c2Thmfy3I",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "13_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "FDP3JzIVSnuJ",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "14_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "GhHYO2LteDmZ",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "15_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "J0baLTpafs7C",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "16_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "uYdb9wWf5Nuv",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "17_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "iSpyhQ5Ya6Nk",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "18_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "MdC0DpifJwu4",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "19_Geo map_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "BDEpqZHDS51s",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"BDEpqZHDS51s"
|
||||
],
|
||||
"title": "Working with notes",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "Working with notes",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "13D1lOc9sqmZ",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"BDEpqZHDS51s",
|
||||
"13D1lOc9sqmZ"
|
||||
],
|
||||
"title": "Exporting as PDF",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "html",
|
||||
"dataFileName": "Exporting as PDF.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "b3v1pLE6TF1Y",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Exporting as PDF_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "xsGM34t8ssKV",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Exporting as PDF_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "cvyes4f1Vhmm",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Exporting as PDF_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "XUG1egT28FBk",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"XUG1egT28FBk"
|
||||
],
|
||||
"title": "Power users",
|
||||
"notePosition": 50,
|
||||
"prefix": null,
|
||||
"isExpanded": true,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "Power users",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "DtJJ20yEozPA",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"XUG1egT28FBk",
|
||||
"DtJJ20yEozPA"
|
||||
],
|
||||
"title": "Theme development",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-palette",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "Theme development",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "5HH79ztN0fZA",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"XUG1egT28FBk",
|
||||
"DtJJ20yEozPA",
|
||||
"5HH79ztN0fZA"
|
||||
],
|
||||
"title": "Creating a custom theme",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "aH8Dk5aMiq7R",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Creating a custom theme.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "bn93hwF7C8sR",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "17p6z24yW5eP",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gXLyv5KXjfxg",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "AJHVfQtIQgJ7",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "on1gD7BzCWdN",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "K3cdwj8f90m0",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "5_Creating a custom theme_im.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "aH8Dk5aMiq7R",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"XUG1egT28FBk",
|
||||
"DtJJ20yEozPA",
|
||||
"aH8Dk5aMiq7R"
|
||||
],
|
||||
"title": "Theme base (legacy vs. next)",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "html",
|
||||
"dataFileName": "Theme base (legacy vs. next).html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "u0zkXkD7rGXA",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Theme base (legacy vs. nex.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "5z4bC0x0eH0P",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Theme base (legacy vs. nex.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "pMq6N1oBV9oo",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"XUG1egT28FBk",
|
||||
"DtJJ20yEozPA",
|
||||
"pMq6N1oBV9oo"
|
||||
],
|
||||
"title": "Reference",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "html",
|
||||
"dataFileName": "Reference.html",
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"noImport": true,
|
||||
"dataFileName": "navigation.html"
|
||||
},
|
||||
{
|
||||
"noImport": true,
|
||||
"dataFileName": "index.html"
|
||||
},
|
||||
{
|
||||
"noImport": true,
|
||||
"dataFileName": "style.css"
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 69 KiB |
@ -0,0 +1,94 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Creating a custom theme</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Creating a custom theme</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<h2>Step 1. Find a place to place the themes</h2>
|
||||
<p>Organization is an important aspect of managing a knowledge base. When
|
||||
developing a new theme or importing an existing one it's a good idea to
|
||||
keep them into one place.</p>
|
||||
<p>As such, the first step is to create a new note to gather all the themes.</p>
|
||||
<p>
|
||||
<img src="Creating a custom theme_im.png" width="181" height="84">
|
||||
</p>
|
||||
<h2>Step 2. Create the theme</h2>
|
||||
<figure class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:32.47%;">
|
||||
<col style="width:67.53%;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:651/220;" src="1_Creating a custom theme_im.png"
|
||||
width="651" height="220">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">Themes are code notes with a special attribute. Start by creating a new
|
||||
code note.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:302/349;" src="2_Creating a custom theme_im.png"
|
||||
width="302" height="349">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">Then change the note type to a CSS code.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:316/133;" src="3_Creating a custom theme_im.png"
|
||||
width="316" height="133">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">In the <i>Owned Attributes</i> section define the <code>#appTheme</code> attribute
|
||||
to point to any desired name. This is the name that will show up in the
|
||||
appearance section in settings.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Step 3. Define the theme's CSS</h2>
|
||||
<p>As a very simple example we will change the background color of the launcher
|
||||
pane to a shade of blue.</p>
|
||||
<p>To alter the different variables of the theme:</p><pre><code class="language-text-css">:root {
|
||||
--launcher-pane-background-color: #0d6efd;
|
||||
}</code></pre>
|
||||
<h2>Step 4. Activating the theme</h2>
|
||||
<p>Refresh the application (Ctrl+Shift+R is a good way to do so) and go to
|
||||
settings. You should see the newly created theme:</p>
|
||||
<p>
|
||||
<img src="4_Creating a custom theme_im.png" width="631" height="481">
|
||||
</p>
|
||||
<p>Afterwards the application will refresh itself with the new theme:</p>
|
||||
<p>
|
||||
<img src="5_Creating a custom theme_im.png" width="653" height="554">
|
||||
</p>
|
||||
<p>Do note that the theme will be based off of the legacy theme. To override
|
||||
that and base the theme on the new TriliumNext theme, see: <a class="reference-link"
|
||||
href="Theme%20base%20(legacy%20vs.%20next).html">Theme base (legacy vs. next)</a>
|
||||
</p>
|
||||
<h2>Step 5. Making changes</h2>
|
||||
<p>Simply go back to the note and change according to needs. To apply the
|
||||
changes to the current window, press Ctrl+Shift+R to refresh.</p>
|
||||
<p>It's a good idea to keep two windows, one for editing and the other one
|
||||
for previewing the changes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 4.7 KiB |
@ -0,0 +1,129 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Reference</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Reference</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<h2>Detecting horizontal vs. vertical layout</h2>
|
||||
<p>The user can select between vertical layout (the classical one, where
|
||||
the launcher bar is on the left) and a horizontal layout (where the launcher
|
||||
bar is on the top and tabs are full-width).</p>
|
||||
<p>Different styles can be applied by using classes at <code>body</code> level:</p><pre><code class="language-text-x-trilium-auto">body.layout-vertical #left-pane {
|
||||
/* Do something */
|
||||
}
|
||||
|
||||
body.layout-horizontal #center-pane {
|
||||
/* Do something else */
|
||||
}</code></pre>
|
||||
<p>The two different layouts use different containers (but they are present
|
||||
in the DOM regardless of the user's choice), for example <code>#horizontal-main-container</code> and <code>#vertical-main-container</code> can
|
||||
be used to customize the background of the content section.</p>
|
||||
<h2>Detecting platform (Windows, macOS) or Electron</h2>
|
||||
<p>It is possible to add particular styles that only apply to a given platform
|
||||
by using the classes in <code>body</code>:</p>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Windows</th>
|
||||
<th>macOS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><pre><code class="language-text-x-trilium-auto">body.platform-win32 {
|
||||
background: red;
|
||||
}</code></pre>
|
||||
</td>
|
||||
<td><pre><code class="language-text-x-trilium-auto">body.platform-darwin {
|
||||
background: red;
|
||||
}</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>It is also possible to only apply a style if running under Electron (desktop
|
||||
application):</p><pre><code class="language-text-x-trilium-auto">body.electron {
|
||||
background: blue;
|
||||
}</code></pre>
|
||||
<h3>Native title bar</h3>
|
||||
<p>It's possible to detect if the user has selected the native title bar
|
||||
or the custom title bar by querying against <code>body</code>:</p><pre><code class="language-text-x-trilium-auto">body.electron.native-titlebar {
|
||||
/* Do something */
|
||||
}
|
||||
|
||||
body.electron:not(.native-titlebar) {
|
||||
/* Do something else */
|
||||
}</code></pre>
|
||||
<h3>Native window buttons</h3>
|
||||
<p>When running under Electron with native title bar off, a feature was introduced
|
||||
to use the platform-specific window buttons such as the semaphore on macOS.</p>
|
||||
<p>See <a href="https://github.com/TriliumNext/Notes/pull/702">Native title bar buttons by eliandoran · Pull Request #702 · TriliumNext/Notes</a> for
|
||||
the original implementation of this feature, including screenshots.</p>
|
||||
<h4>On Windows</h4>
|
||||
<p>The colors of the native window button area can be adjusted using a RGB
|
||||
hex color:</p><pre><code class="language-text-x-trilium-auto">body {
|
||||
--native-titlebar-foreground: #ffffff;
|
||||
--native-titlebar-background: #ff0000;
|
||||
}</code></pre>
|
||||
<p>It is also possible to use transparency at the cost of reduced hover colors
|
||||
using a RGBA hex color:</p><pre><code class="language-text-x-trilium-auto">body {
|
||||
--native-titlebar-background: #ff0000aa;
|
||||
}</code></pre>
|
||||
<p>Note that the value is read when the window is initialized and then it
|
||||
is refreshed only when the user changes their light/dark mode preference.</p>
|
||||
<h4>On macOS</h4>
|
||||
<p>On macOS the semaphore window buttons are enabled by default when the
|
||||
native title bar is disabled. The offset of the buttons can be adjusted
|
||||
using:</p><pre><code class="language-text-x-trilium-auto">body {
|
||||
--native-titlebar-darwin-x-offset: 12;
|
||||
--native-titlebar-darwin-y-offset: 14 !important;
|
||||
}</code></pre>
|
||||
<h3>Background/transparency effects on Windows (Mica)</h3>
|
||||
<p>Windows 11 offers a special background/transparency effect called Mica,
|
||||
which can be enabled by themes by setting the <code>--background-material</code> variable
|
||||
at <code>body</code> level:</p><pre><code class="language-text-x-trilium-auto">body.electron.platform-win32 {
|
||||
--background-material: tabbed;
|
||||
}</code></pre>
|
||||
<p>The value can be either <code>tabbed</code> (especially useful for the horizontal
|
||||
layout) or <code>mica</code> (ideal for the vertical layout).</p>
|
||||
<p>Do note that the Mica effect is applied at <code>body</code> level and the
|
||||
theme needs to make the entire hierarchy (semi-)transparent in order for
|
||||
it to be visible. Use the TrilumNext theme as an inspiration.</p>
|
||||
<h2>Note icons, tab workspace accent color</h2>
|
||||
<p>Theme capabilities are small adjustments done through CSS variables that
|
||||
can affect the layout or the visual aspect of the application.</p>
|
||||
<p>In the tab bar, to display the icons of notes instead of the icon of the
|
||||
workspace:</p><pre><code class="language-text-x-trilium-auto">:root {
|
||||
--tab-note-icons: true;
|
||||
}</code></pre>
|
||||
<p>When a workspace is hoisted for a given tab, it is possible to get the
|
||||
background color of that workspace, for example to apply a small strip
|
||||
on the tab instead of the whole background color:</p><pre><code class="language-text-x-trilium-auto">.note-tab .note-tab-wrapper {
|
||||
--tab-background-color: initial !important;
|
||||
}
|
||||
|
||||
.note-tab .note-tab-wrapper::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background-color: var(--workspace-tab-background-color);
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,36 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Theme base (legacy vs. next)</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Theme base (legacy vs. next)</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<p>By default, any custom theme will be based on the legacy light theme.
|
||||
To change the TriliumNext theme instead, add the <code>#appThemeBase=next</code> attribute
|
||||
onto the existing theme. The <code>appTheme</code> attribute must also be
|
||||
present.</p>
|
||||
<p>
|
||||
<img src="1_Theme base (legacy vs. nex.png" width="424" height="140">
|
||||
</p>
|
||||
<p>When <code>appThemeBase</code> is set to <code>next</code> it will use the
|
||||
“TriliumNext (auto)” theme. Any other value is ignored and will use the
|
||||
legacy white theme instead.</p>
|
||||
<h2>Overrides</h2>
|
||||
<p>Do note that the TriliumNext theme has a few more overrides than the legacy
|
||||
theme, so you might need to suffix <code>!important</code> if the style changes
|
||||
are not applied.</p><pre><code class="language-text-css">:root {
|
||||
--launcher-pane-background-color: #0d6efd !important;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 191 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 515 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 323 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 397 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 43 KiB |
@ -0,0 +1,295 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Geo map</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Geo map</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<h2>Creating a new geo map</h2>
|
||||
<figure class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:4.67%;">
|
||||
<col style="width:57.81%;">
|
||||
<col style="width:37.52%;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1256/1044;" src="Geo map_image.png" width="1256"
|
||||
height="1044">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">Right click on any note on the note tree and select <i>Insert child note</i> → <i>Geo Map (beta)</i>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1720/1396;" src="1_Geo map_image.png" width="1720"
|
||||
height="1396">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">By default the map will be empty and will show the entire world.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Repositioning the map</h2>
|
||||
<ul>
|
||||
<li>Click and drag the map in order to move across the map.</li>
|
||||
<li>Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons
|
||||
on the top-left to adjust the zoom.</li>
|
||||
</ul>
|
||||
<p>The position on the map and the zoom are saved inside the map note. When
|
||||
visting again the note it will restore this position.</p>
|
||||
<h2>Adding a marker using the map</h2>
|
||||
<figure class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:5.05%;">
|
||||
<col style="width:49.62%;">
|
||||
<col style="width:45.33%;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>1</th>
|
||||
<td> </td>
|
||||
<td style="vertical-align:top;">
|
||||
<p>To create a marker, first navigate to the desired point on the map. Then
|
||||
press the
|
||||
<img class="image_resized" style="aspect-ratio:72/66;width:7.37%;"
|
||||
src="2_Geo map_image.png" width="72" height="66">button on the top-right of the map.</p>
|
||||
<p>If the button is not visible, make sure the button section is visible
|
||||
by pressing the chevron button (
|
||||
<img class="image_resized" style="aspect-ratio:72/66;width:7.51%;"
|
||||
src="3_Geo map_image.png" width="72" height="66">) in the top-right of the map.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1730/416;" src="4_Geo map_image.png" width="1730"
|
||||
height="416">
|
||||
</figure>
|
||||
<p> </p>
|
||||
</td>
|
||||
<td style="vertical-align:top;">
|
||||
<p>Once pressed, the map will enter in the insert mode, as illustrated by
|
||||
the notification.</p>
|
||||
<p>Simply click the point on the map where to place the marker, or the Escape
|
||||
key to cancel.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1586/404;" src="5_Geo map_image.png" width="1586"
|
||||
height="404">
|
||||
</figure>
|
||||
<p> </p>
|
||||
</td>
|
||||
<td>Enter the name of the marker/note to be created. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>4</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1696/608;" src="6_Geo map_image.png" width="1696"
|
||||
height="608">
|
||||
</figure>
|
||||
<p> </p>
|
||||
</td>
|
||||
<td>Once confirmed, the marker will show up on the map and it will also be
|
||||
displayed as a child note of the map.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Repositioning markers</h2>
|
||||
<p>It's possible to reposition existing markers by simply drag and dropping
|
||||
them to the new destination.</p>
|
||||
<p>As soon as the mouse is released, the new position is saved.</p>
|
||||
<p>If moved by mistake, there is currently no way to undo the change. If
|
||||
the mouse was not yet released, it's possible to force a refresh of the
|
||||
page (Ctrl+R or Meta+R) to cancel it.</p>
|
||||
<h2>Adding the geolocation manually</h2>
|
||||
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
||||
of the child notes:</p>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1288/278;" src="7_Geo map_image.png" width="1288"
|
||||
height="278">
|
||||
</figure>
|
||||
<p>The value of the attribute is made up of the latitude and longitude separated
|
||||
by a comma.</p>
|
||||
<h3>Adding from Google Maps</h3>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:732/918;" src="8_Geo map_image.png" width="732"
|
||||
height="918">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">
|
||||
<p>Go to Google Maps on the web and look for a desired location, right click
|
||||
on it and a context menu will show up.</p>
|
||||
<p>Simply click on the first item displaying the coordinates and they will
|
||||
be copied to clipboard.</p>
|
||||
<p>Then paste the value inside the text box into the <code>#geolocation</code> attribute
|
||||
of a child note of the map (don't forget to surround the value with a <code>"</code> character).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:518/84;" src="11_Geo map_image.png" width="518"
|
||||
height="84">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">
|
||||
<p>In Trilium, create a child note under the map.</p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1074/276;" src="9_Geo map_image.png" width="1074"
|
||||
height="276">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">And then go to Owned Attributes and type <code>#geolocation="</code>, then
|
||||
paste from the clipboard as-is and then add the ending <code>"</code> character.
|
||||
Press Enter to confirm and the map should now be updated to contain the
|
||||
new note.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h3>Adding from OpenStreetMap</h3>
|
||||
<p>Similarly to the Google Maps approach:</p>
|
||||
<figure class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:4.65%;">
|
||||
<col style="width:36.01%;">
|
||||
<col style="width:59.34%;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:562/454;" src="12_Geo map_image.png" width="562"
|
||||
height="454">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">Go to any location on openstreetmap.org and right click to bring up the
|
||||
context menu. Select the “Show address” item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:696/480;" src="13_Geo map_image.png" width="696"
|
||||
height="480">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">
|
||||
<p>The address will be visible in the top-left of the screen, in the place
|
||||
of the search bar.</p>
|
||||
<p>Select the coordinates and copy them into the clipboard.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:640/276;" src="14_Geo map_image.png" width="640"
|
||||
height="276">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">Simply paste the value inside the text box into the <code>#geolocation</code> attribute
|
||||
of a child note of the map and then it should be displayed on the map.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Adding GPS tracks (.gpx)</h2>
|
||||
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
|
||||
<figure
|
||||
class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:4.66%;">
|
||||
<col style="width:36.79%;">
|
||||
<col style="width:58.55%;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:226/74;" src="17_Geo map_image.png" width="226"
|
||||
height="74">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">To add a track, simply drag & drop a .gpx file inside the geo map
|
||||
in the note tree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:322/222;" src="18_Geo map_image.png" width="322"
|
||||
height="222">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">In order for the file to be recognized as a GPS track, it needs to show
|
||||
up as <code>application/gpx+xml</code> in the <i>File type</i> field.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:620/530;" src="19_Geo map_image.png" width="620"
|
||||
height="530">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">
|
||||
<p>When going back to the map, the track should now be visible.</p>
|
||||
<p>The start and end points of the track are indicated by the two blue markers.</p>
|
||||
<p> </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 260 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 26 KiB |
@ -0,0 +1,52 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Exporting as PDF</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Exporting as PDF</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<figure class="image image-style-align-right image_resized" style="width:47.17%;">
|
||||
<img style="aspect-ratio:951/432;" src="1_Exporting as PDF_image.png"
|
||||
width="951" height="432">
|
||||
</figure>
|
||||
<p>On the desktop application of Trilium it is possible to export a note
|
||||
as PDF. On the server or PWA (mobile), the option is not available due
|
||||
to technical constraints and it will be hidden.</p>
|
||||
<p>To print a note, select the
|
||||
<img src="Exporting as PDF_image.png" width="29"
|
||||
height="31">button to the right of the note and select <i>Export as PDF</i>.</p>
|
||||
<p>Afterwards you will be prompted to select where to save the PDF file.
|
||||
Upon confirmation, the resulting PDF will be opened automatically.</p>
|
||||
<p>Should you encounter any visual issues in the resulting PDF file (e.g.
|
||||
a table does not fit properly, there is cut off text, etc.) feel free to
|
||||
<a
|
||||
href="#root/OeKBfN6JbMIq/jRV1MPt4mNSP/hrC6xn7hnDq5">report the issue</a>. In this case, it's best to offer a sample note (click
|
||||
on the
|
||||
<img src="Exporting as PDF_image.png" width="29" height="31">button, select Export note → This note and all of its descendants → HTML
|
||||
in ZIP archive). Make sure not to accidentally leak any personal information.</p>
|
||||
<h2>Landscape mode</h2>
|
||||
<p>When exporting to PDF, there are no customizable settings such as page
|
||||
orientation, size, etc. However, it is possible to specify a given note
|
||||
to be printed as a PDF in landscape mode by adding the <code>#printLandscape</code> attribute
|
||||
to it (see <a class="reference-link" href="#root/9QRytp0ZYFIf/PnO38wN0ffOA">Adding an attribute to a note</a>).</p>
|
||||
<h2>Page size</h2>
|
||||
<p>By default, the resulting PDF will be in Letter format. It is possible
|
||||
to adjust it to another page size via the <code>#printPageSize</code> attribute,
|
||||
with one of the following values: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.</p>
|
||||
<h2>Keyboard shortcut</h2>
|
||||
<p>It's possible to trigger the export to PDF from the keyboard by going
|
||||
to <i>Keyboard shortcuts</i> and assigning a key combination
|
||||
for the <code>exportAsPdf</code> action.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 340 B |
11
src/public/app/doc_notes/en/User Guide/index.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<frameset cols="25%,75%">
|
||||
<frame name="navigation" src="navigation.html">
|
||||
<frame name="detail" src="User%20Guide/Types%20of%20notes/Geo%20map.html">
|
||||
</frameset>
|
||||
</html>
|
47
src/public/app/doc_notes/en/User Guide/navigation.html
Normal file
@ -0,0 +1,47 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul>
|
||||
<li>User Guide
|
||||
<ul>
|
||||
<li>Types of notes
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Types%20of%20notes/Geo%20map.html" target="detail">Geo map</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Working with notes
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Working%20with%20notes/Exporting%20as%20PDF.html"
|
||||
target="detail">Exporting as PDF</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Power users
|
||||
<ul>
|
||||
<li>Theme development
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Power%20users/Theme%20development/Creating%20a%20custom%20theme.html"
|
||||
target="detail">Creating a custom theme</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Power%20users/Theme%20development/Theme%20base%20(legacy%20vs.%20next).html"
|
||||
target="detail">Theme base (legacy vs. next)</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Power%20users/Theme%20development/Reference.html"
|
||||
target="detail">Reference</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
|
||||
</html>
|
567
src/public/app/doc_notes/en/User Guide/style.css
Normal file
@ -0,0 +1,567 @@
|
||||
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
|
||||
|
||||
.printed-content .ck-widget__selection-handle, .printed-content .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-break {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.printed-content .page-break:after,
|
||||
.printed-content .page-break > * {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ck-content li p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* CKEditor 5 (v41.0.0) content styles.
|
||||
* Generated on Fri, 26 Jan 2024 10:23:49 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ck-color-image-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
|
||||
--ck-color-mention-text: hsl(341, 100%, 30%);
|
||||
--ck-color-selector-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-selector-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
||||
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
|
||||
--ck-highlight-pen-green: hsl(112, 100%, 27%);
|
||||
--ck-highlight-pen-red: hsl(0, 85%, 49%);
|
||||
--ck-image-style-spacing: 1.5em;
|
||||
--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);
|
||||
--ck-todo-list-checkmark-size: 16px;
|
||||
}
|
||||
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table .ck-table-resized {
|
||||
table-layout: fixed;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table table {
|
||||
overflow: hidden;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table td,
|
||||
.ck-content .table th {
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 0.9em auto;
|
||||
display: table;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table td,
|
||||
.ck-content .table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border: 1px solid hsl(0, 0%, 75%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table th {
|
||||
font-weight: bold;
|
||||
background: hsla(0, 0%, 0%, 5%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="rtl"] .table th {
|
||||
text-align: right;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="ltr"] .table th {
|
||||
text-align: left;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
||||
.ck-content .table > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: top;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
color: var(--ck-color-selector-caption-text);
|
||||
background-color: var(--ck-color-selector-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: hsl(0, 0%, 100%);
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 0.9em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list {
|
||||
list-style: none;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list li {
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list li .todo-list {
|
||||
margin-top: 5px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input {
|
||||
-webkit-appearance: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: var(--ck-todo-list-checkmark-size);
|
||||
height: var(--ck-todo-list-checkmark-size);
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
left: -25px;
|
||||
margin-right: -15px;
|
||||
right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content[dir=rtl] .todo-list .todo-list__label > input {
|
||||
left: 0;
|
||||
margin-right: 0;
|
||||
right: -25px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid hsl(0, 0%, 20%);
|
||||
border-radius: 2px;
|
||||
transition: 250ms ease-in-out box-shadow;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input[checked]::before {
|
||||
background: hsl(126, 64%, 41%);
|
||||
border-color: hsl(126, 64%, 41%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input[checked]::after {
|
||||
border-color: hsl(0, 0%, 100%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label .todo-list__label__description {
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
||||
position: absolute;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input,
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
||||
cursor: pointer;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before {
|
||||
box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
||||
-webkit-appearance: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: var(--ck-todo-list-checkmark-size);
|
||||
height: var(--ck-todo-list-checkmark-size);
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
left: -25px;
|
||||
margin-right: -15px;
|
||||
right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input {
|
||||
left: 0;
|
||||
margin-right: 0;
|
||||
right: -25px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid hsl(0, 0%, 20%);
|
||||
border-radius: 2px;
|
||||
transition: 250ms ease-in-out box-shadow;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before {
|
||||
background: hsl(126, 64%, 41%);
|
||||
border-color: hsl(126, 64%, 41%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after {
|
||||
border-color: hsl(0, 0%, 100%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
||||
position: absolute;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol {
|
||||
list-style-type: lower-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol {
|
||||
list-style-type: upper-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol ol {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 0.9em auto;
|
||||
min-width: 50px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline {
|
||||
/*
|
||||
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
|
||||
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
|
||||
* This strange behavior does not happen with inline-flex.
|
||||
*/
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline picture {
|
||||
display: flex;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline picture,
|
||||
.ck-content .image-inline img {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content img.image_resized {
|
||||
height: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized img {
|
||||
width: 100%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: var(--ck-color-image-caption-text);
|
||||
background-color: var(--ck-color-image-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-left,
|
||||
.ck-content .image-style-block-align-right {
|
||||
max-width: calc(100% - var(--ck-image-style-spacing));
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left,
|
||||
.ck-content .image-style-align-right {
|
||||
clear: none;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
max-width: 50%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left {
|
||||
float: left;
|
||||
margin-right: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-right {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-right {
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-left {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content p + .image-style-align-left,
|
||||
.ck-content p + .image-style-align-right,
|
||||
.ck-content p + .image-style-side {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-left,
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-top: var(--ck-inline-image-style-spacing);
|
||||
margin-bottom: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-left {
|
||||
margin-right: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-left: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-yellow {
|
||||
background-color: var(--ck-highlight-marker-yellow);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-green {
|
||||
background-color: var(--ck-highlight-marker-green);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-pink {
|
||||
background-color: var(--ck-highlight-marker-pink);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-blue {
|
||||
background-color: var(--ck-highlight-marker-blue);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-red {
|
||||
color: var(--ck-highlight-pen-red);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-green {
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
padding-right: 1.5em;
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
border-left: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content[dir="rtl"] blockquote {
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-tiny {
|
||||
font-size: .7em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-small {
|
||||
font-size: .85em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-big {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-huge {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-mention/theme/mention.css */
|
||||
.ck-content .mention {
|
||||
background: var(--ck-color-mention-background);
|
||||
color: var(--ck-color-mention-text);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
.ck-content hr {
|
||||
margin: 15px 0;
|
||||
height: 4px;
|
||||
background: hsl(0, 0%, 87%);
|
||||
border: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre {
|
||||
padding: 1em;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
tab-size: 4;
|
||||
white-space: pre-wrap;
|
||||
font-style: normal;
|
||||
min-width: 200px;
|
||||
border: 0px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.ck-content pre:not(.hljs) {
|
||||
color: hsl(0, 0%, 20.8%);
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre code {
|
||||
background: unset;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
@media print {
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
padding: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -86,6 +86,7 @@ import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolb
|
||||
import options from "../services/options.js";
|
||||
import utils from "../services/utils.js";
|
||||
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
|
||||
import ContextualHelpButton from "../widgets/floating_buttons/help_button.js";
|
||||
|
||||
export default class DesktopLayout {
|
||||
constructor(customWidgets) {
|
||||
@ -205,6 +206,7 @@ export default class DesktopLayout {
|
||||
.child(new CopyImageReferenceButton())
|
||||
.child(new SvgExportButton())
|
||||
.child(new BacklinksWidget())
|
||||
.child(new ContextualHelpButton())
|
||||
.child(new HideFloatingButtonsButton())
|
||||
)
|
||||
.child(new MermaidWidget())
|
||||
|
@ -52,7 +52,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
const noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node);
|
||||
|
||||
const notSearch = note?.type !== "search";
|
||||
const notOptions = !note?.noteId.startsWith("_options");
|
||||
const notOptionsOrHelp = !note?.noteId.startsWith("_options") && !note?.noteId.startsWith("_help");
|
||||
const parentNotSearch = !parentNote || parentNote.type !== "search";
|
||||
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
||||
|
||||
@ -80,7 +80,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
command: "insertNoteAfter",
|
||||
uiIcon: "bx bx-plus",
|
||||
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
|
||||
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions
|
||||
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp
|
||||
},
|
||||
|
||||
{
|
||||
@ -88,7 +88,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
command: "insertChildNote",
|
||||
uiIcon: "bx bx-plus",
|
||||
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
|
||||
enabled: notSearch && noSelectedNotes && notOptions
|
||||
enabled: notSearch && noSelectedNotes && notOptionsOrHelp
|
||||
},
|
||||
|
||||
{ title: "----" },
|
||||
@ -112,14 +112,14 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`,
|
||||
command: "editBranchPrefix",
|
||||
uiIcon: "bx bx-rename",
|
||||
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions
|
||||
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
|
||||
},
|
||||
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptions },
|
||||
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },
|
||||
{
|
||||
title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`,
|
||||
command: "duplicateSubtree",
|
||||
uiIcon: "bx bx-outline",
|
||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions
|
||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
||||
},
|
||||
|
||||
{ title: "----" },
|
||||
@ -136,7 +136,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
{ title: "----" },
|
||||
|
||||
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true },
|
||||
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions }
|
||||
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptionsOrHelp }
|
||||
]
|
||||
},
|
||||
|
||||
@ -178,14 +178,14 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
|
||||
command: "deleteNotes",
|
||||
uiIcon: "bx bx-trash destructive-action-icon",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp
|
||||
},
|
||||
|
||||
{ title: "----" },
|
||||
|
||||
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
|
||||
|
||||
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
|
||||
|
||||
{ title: "----" },
|
||||
|
||||
|
@ -79,7 +79,7 @@ async function renderAttributes(attributes: FAttribute[], renderIsInheritable: b
|
||||
return $container;
|
||||
}
|
||||
|
||||
const HIDDEN_ATTRIBUTES = ["originalFileName", "fileSize", "template", "inherit", "cssClass", "iconClass", "pageSize", "viewType", "geolocation"];
|
||||
const HIDDEN_ATTRIBUTES = [ "originalFileName", "fileSize", "template", "inherit", "cssClass", "iconClass", "pageSize", "viewType", "geolocation", "docName" ];
|
||||
|
||||
async function renderNormalAttributes(note: FNote) {
|
||||
const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes();
|
||||
|
@ -25,14 +25,29 @@ async function getLinkIcon(noteId: string, viewMode: ViewMode | undefined) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
type ViewMode = "default" | "source" | "attachments" | string;
|
||||
// TODO: Remove `string` once all the view modes have been mapped.
|
||||
type ViewMode = "default" | "source" | "attachments" | "contextual-help" | string;
|
||||
|
||||
export interface ViewScope {
|
||||
/**
|
||||
* - "source", when viewing the source code of a note.
|
||||
* - "attachments", when viewing the attachments of a note.
|
||||
* - "contextual-help", if the current view represents a help window that was opened to the side of the main content.
|
||||
* - "default", otherwise.
|
||||
*/
|
||||
viewMode?: ViewMode;
|
||||
attachmentId?: string;
|
||||
readOnlyTemporarilyDisabled?: boolean;
|
||||
highlightsListPreviousVisible?: boolean;
|
||||
highlightsListTemporarilyHidden?: boolean;
|
||||
tocTemporarilyHidden?: boolean;
|
||||
/*
|
||||
* The reason for adding tocPreviousVisible is to record whether the previous state of the toc is hidden or displayed,
|
||||
* and then let it be displayed/hidden at the initial time. If there is no such value,
|
||||
* when the right panel needs to display highlighttext but not toc, every time the note content is changed,
|
||||
* toc will appear and then close immediately, because getToc(html) function will consume time
|
||||
*/
|
||||
tocPreviousVisible?: boolean;
|
||||
}
|
||||
|
||||
interface CreateLinkOptions {
|
||||
|
@ -22,11 +22,7 @@ interface CreateNoteOpts {
|
||||
focus?: "title" | "content";
|
||||
target?: string;
|
||||
targetBranchId?: string;
|
||||
textEditor?: {
|
||||
// TODO: Replace with interface once note_context.js is converted.
|
||||
getSelectedHtml(): string;
|
||||
removeSelection(): void;
|
||||
};
|
||||
textEditor?: TextEditor;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
|
3
src/public/app/types.d.ts
vendored
@ -239,6 +239,9 @@ declare global {
|
||||
},
|
||||
getData(): string;
|
||||
setData(data: string): void;
|
||||
getSelectedHtml(): string;
|
||||
removeSelection(): void;
|
||||
sourceElement: HTMLElement;
|
||||
}
|
||||
|
||||
interface MentionItem {
|
||||
|
@ -226,6 +226,12 @@ const TPL = `
|
||||
<kbd data-command="showHelp"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-help-button" data-trigger-command="showCheatsheet">
|
||||
<span class="bx bxs-keyboard"></span>
|
||||
${t("global_menu.show-cheatsheet")}
|
||||
<kbd data-command="showCheatsheet"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-about-dialog-button">
|
||||
<span class="bx bx-info-circle"></span>
|
||||
${t("global_menu.about")}
|
||||
|
@ -61,7 +61,7 @@ export default class SplitNoteContainer extends FlexContainer {
|
||||
await appContext.tabManager.activateNoteContext(noteContext.ntxId);
|
||||
|
||||
if (notePath) {
|
||||
await noteContext.setNote(notePath, viewScope);
|
||||
await noteContext.setNote(notePath, { viewScope });
|
||||
} else {
|
||||
await noteContext.setEmpty();
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ export default class HelpDialog extends BasicWidget {
|
||||
this.$widget = $(TPL);
|
||||
}
|
||||
|
||||
showHelpEvent() {
|
||||
showCheatsheetEvent() {
|
||||
utils.openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ const TPL = `
|
||||
}
|
||||
|
||||
.floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) {
|
||||
margin-left: 10px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.floating-buttons-children > button, .floating-buttons-children .floating-button {
|
||||
|
@ -5,6 +5,7 @@ const TPL = `\
|
||||
<div class="geo-map-buttons">
|
||||
<style>
|
||||
.geo-map-buttons {
|
||||
contain: none;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
@ -12,13 +13,6 @@ const TPL = `\
|
||||
.leaflet-pane {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.geo-map-buttons {
|
||||
contain: none;
|
||||
background: var(--main-background-color);
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button"
|
||||
|
76
src/public/app/widgets/floating_buttons/help_button.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import appContext from "../../components/app_context.js";
|
||||
import type { NoteType } from "../../entities/fnote.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { ViewScope } from "../../services/link.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<button class="open-contextual-help-button" title="${t("help-button.title")}">
|
||||
<span class="bx bx-help-circle"></span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
const byNoteType: Record<NoteType, string | null> = {
|
||||
book: null,
|
||||
canvas: null,
|
||||
code: null,
|
||||
contentWidget: null,
|
||||
doc: null,
|
||||
file: null,
|
||||
geoMap: "foPEtsL51pD2",
|
||||
image: null,
|
||||
launcher: null,
|
||||
mermaid: null,
|
||||
mindMap: null,
|
||||
noteMap: null,
|
||||
relationMap: null,
|
||||
render: null,
|
||||
search: null,
|
||||
text: null,
|
||||
webView: null
|
||||
};
|
||||
|
||||
export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
|
||||
private helpNoteIdToOpen?: string | null;
|
||||
|
||||
isEnabled() {
|
||||
this.helpNoteIdToOpen = null;
|
||||
|
||||
if (!super.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.note && byNoteType[this.note.type]) {
|
||||
this.helpNoteIdToOpen = byNoteType[this.note.type];
|
||||
}
|
||||
|
||||
return !!this.helpNoteIdToOpen;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("click", () => {
|
||||
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
|
||||
const targetNote = `_help_${this.helpNoteIdToOpen}`;
|
||||
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
|
||||
const viewScope: ViewScope = {
|
||||
viewMode: "contextual-help",
|
||||
};
|
||||
if (!helpSubcontext) {
|
||||
// The help is not already open, open a new split with it.
|
||||
const { ntxId } = subContexts[subContexts.length - 1];
|
||||
this.triggerCommand("openNewNoteSplit", {
|
||||
ntxId,
|
||||
notePath: targetNote,
|
||||
hoistedNoteId: "_help",
|
||||
viewScope
|
||||
})
|
||||
} else {
|
||||
// There is already a help window open, make sure it opens on the right note.
|
||||
helpSubcontext.setNote(targetNote, { viewScope });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@ import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import SpacedUpdate from "../services/spaced_update.js";
|
||||
import server from "../services/server.js";
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
|
||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||
import noteCreateService from "../services/note_create.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
@ -33,6 +33,8 @@ import MindMapWidget from "./type_widgets/mind_map.js";
|
||||
import { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js";
|
||||
import GeoMapTypeWidget from "./type_widgets/geo_map.js";
|
||||
import utils from "../services/utils.js";
|
||||
import type { NoteType } from "../entities/fnote.js";
|
||||
import type TypeWidget from "./type_widgets/type_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail">
|
||||
@ -73,14 +75,34 @@ const typeWidgetClasses = {
|
||||
geoMap: GeoMapTypeWidget
|
||||
};
|
||||
|
||||
/**
|
||||
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
|
||||
* for protected session or attachment information.
|
||||
*/
|
||||
type ExtendedNoteType = Exclude<NoteType, "mermaid" | "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession";
|
||||
|
||||
export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
private typeWidgets: Record<string, TypeWidget>;
|
||||
private spacedUpdate: SpacedUpdate;
|
||||
private type?: ExtendedNoteType;
|
||||
private mime?: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.typeWidgets = {};
|
||||
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
if (!this.noteContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { note } = this.noteContext;
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { noteId } = note;
|
||||
|
||||
const data = await this.getTypeWidget().getData();
|
||||
@ -94,7 +116,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
await server.put(`notes/${noteId}/data`, data, this.componentId);
|
||||
|
||||
this.getTypeWidget().dataSaved?.();
|
||||
this.getTypeWidget().dataSaved();
|
||||
});
|
||||
|
||||
appContext.addBeforeUnloadListener(this);
|
||||
@ -129,13 +151,17 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$widget.append($renderedWidget);
|
||||
|
||||
if (this.noteContext) {
|
||||
await typeWidget.handleEvent("setNoteContext", { noteContext: this.noteContext });
|
||||
}
|
||||
|
||||
// this is happening in update(), so note has been already set, and we need to reflect this
|
||||
if (this.noteContext) {
|
||||
await typeWidget.handleEvent("noteSwitched", {
|
||||
noteContext: this.noteContext,
|
||||
notePath: this.noteContext.notePath
|
||||
});
|
||||
}
|
||||
|
||||
this.child(typeWidget);
|
||||
}
|
||||
@ -150,57 +176,60 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
// https://github.com/zadam/trilium/issues/2522
|
||||
const isBackendNote = this.noteContext?.noteId === "_backendLog";
|
||||
const isSqlNote = this.mime === "text/x-sqlite;schema=trilium";
|
||||
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "geoMap"].includes(this.type);
|
||||
const isFullHeight = (!this.noteContext.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|
||||
|| this.noteContext.viewScope.viewMode === "attachments"
|
||||
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "geoMap"].includes(this.type ?? "");
|
||||
const isFullHeight = (!this.noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|
||||
|| this.noteContext?.viewScope?.viewMode === "attachments"
|
||||
|| isBackendNote;
|
||||
|
||||
this.$widget.toggleClass("full-height", isFullHeight);
|
||||
}
|
||||
|
||||
getTypeWidget() {
|
||||
if (!this.typeWidgets[this.type]) {
|
||||
if (!this.type || !this.typeWidgets[this.type]) {
|
||||
throw new Error(t(`note_detail.could_not_find_typewidget`, { type: this.type }));
|
||||
}
|
||||
|
||||
return this.typeWidgets[this.type];
|
||||
}
|
||||
|
||||
async getWidgetType() {
|
||||
async getWidgetType(): Promise<ExtendedNoteType> {
|
||||
const note = this.note;
|
||||
|
||||
if (!note) {
|
||||
return "empty";
|
||||
}
|
||||
|
||||
let type = note.type;
|
||||
const viewScope = this.noteContext.viewScope;
|
||||
let type: NoteType = note.type;
|
||||
let resultingType: ExtendedNoteType;
|
||||
const viewScope = this.noteContext?.viewScope;
|
||||
|
||||
if (viewScope.viewMode === "source") {
|
||||
type = "readOnlyCode";
|
||||
} else if (viewScope.viewMode === "attachments") {
|
||||
type = viewScope.attachmentId ? "attachmentDetail" : "attachmentList";
|
||||
} else if (type === "text" && (await this.noteContext.isReadOnly())) {
|
||||
type = "readOnlyText";
|
||||
} else if ((type === "code" || type === "mermaid") && (await this.noteContext.isReadOnly())) {
|
||||
type = "readOnlyCode";
|
||||
if (viewScope?.viewMode === "source") {
|
||||
resultingType = "readOnlyCode";
|
||||
} else if (viewScope && viewScope.viewMode === "attachments") {
|
||||
resultingType = viewScope.attachmentId ? "attachmentDetail" : "attachmentList";
|
||||
} else if (type === "text" && (await this.noteContext?.isReadOnly())) {
|
||||
resultingType = "readOnlyText";
|
||||
} else if ((type === "code" || type === "mermaid") && (await this.noteContext?.isReadOnly())) {
|
||||
resultingType = "readOnlyCode";
|
||||
} else if (type === "text") {
|
||||
type = "editableText";
|
||||
resultingType = "editableText";
|
||||
} else if (type === "code" || type === "mermaid") {
|
||||
type = "editableCode";
|
||||
resultingType = "editableCode";
|
||||
} else if (type === "launcher") {
|
||||
type = "doc";
|
||||
resultingType = "doc";
|
||||
} else {
|
||||
resultingType = type;
|
||||
}
|
||||
|
||||
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
type = "protectedSession";
|
||||
resultingType = "protectedSession";
|
||||
}
|
||||
|
||||
return type;
|
||||
return resultingType;
|
||||
}
|
||||
|
||||
async focusOnDetailEvent({ ntxId }) {
|
||||
if (this.noteContext.ntxId !== ntxId) {
|
||||
async focusOnDetailEvent({ ntxId }: EventData<"focusOnDetail">) {
|
||||
if (this.noteContext?.ntxId !== ntxId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -210,8 +239,8 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
widget.focus();
|
||||
}
|
||||
|
||||
async scrollToEndEvent({ ntxId }) {
|
||||
if (this.noteContext.ntxId !== ntxId) {
|
||||
async scrollToEndEvent({ ntxId }: EventData<"scrollToEnd">) {
|
||||
if (this.noteContext?.ntxId !== ntxId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -224,29 +253,29 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async beforeNoteSwitchEvent({ noteContext }) {
|
||||
async beforeNoteSwitchEvent({ noteContext }: EventData<"beforeNoteSwitch">) {
|
||||
if (this.isNoteContext(noteContext.ntxId)) {
|
||||
await this.spacedUpdate.updateNowIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
async beforeNoteContextRemoveEvent({ ntxIds }) {
|
||||
async beforeNoteContextRemoveEvent({ ntxIds }: EventData<"beforeNoteContextRemove">) {
|
||||
if (this.isNoteContext(ntxIds)) {
|
||||
await this.spacedUpdate.updateNowIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
async runActiveNoteCommand(params) {
|
||||
async runActiveNoteCommand(params: CommandListenerData<"runActiveNote">) {
|
||||
if (this.isNoteContext(params.ntxId)) {
|
||||
// make sure that script is saved before running it #4028
|
||||
await this.spacedUpdate.updateNowIfNecessary();
|
||||
}
|
||||
|
||||
return await this.parent.triggerCommand("runActiveNote", params);
|
||||
return await this.parent?.triggerCommand("runActiveNote", params);
|
||||
}
|
||||
|
||||
async printActiveNoteEvent() {
|
||||
if (!this.noteContext.isActive()) {
|
||||
if (!this.noteContext?.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -254,7 +283,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async exportAsPdfEvent() {
|
||||
if (!this.noteContext.isActive()) {
|
||||
if (!this.noteContext?.isActive() || !this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -266,18 +295,18 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
});
|
||||
}
|
||||
|
||||
hoistedNoteChangedEvent({ ntxId }) {
|
||||
hoistedNoteChangedEvent({ ntxId }: EventData<"hoistedNoteChanged">) {
|
||||
if (this.isNoteContext(ntxId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
// we're detecting note type change on the note_detail level, but triggering the noteTypeMimeChanged
|
||||
// globally, so it gets also to e.g. ribbon components. But this means that the event can be generated multiple
|
||||
// times if the same note is open in several tabs.
|
||||
|
||||
if (loadResults.isNoteContentReloaded(this.noteId, this.componentId)) {
|
||||
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId, this.componentId)) {
|
||||
// probably incorrect event
|
||||
// calling this.refresh() is not enough since the event needs to be propagated to children as well
|
||||
// FIXME: create a separate event to force hierarchical refresh
|
||||
@ -285,7 +314,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
// this uses handleEvent to make sure that the ordinary content updates are propagated only in the subtree
|
||||
// to avoid the problem in #3365
|
||||
this.handleEvent("noteTypeMimeChanged", { noteId: this.noteId });
|
||||
} else if (loadResults.isNoteReloaded(this.noteId, this.componentId) && (this.type !== (await this.getWidgetType()) || this.mime !== this.note.mime)) {
|
||||
} else if (this.noteId && loadResults.isNoteReloaded(this.noteId, this.componentId) && (this.type !== (await this.getWidgetType()) || this.mime !== this.note?.mime)) {
|
||||
// this needs to have a triggerEvent so that e.g., note type (not in the component subtree) is updated
|
||||
this.triggerEvent("noteTypeMimeChanged", { noteId: this.noteId });
|
||||
} else {
|
||||
@ -293,12 +322,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
const label = attrs.find(
|
||||
(attr) =>
|
||||
attr.type === "label" && ["readOnly", "autoReadOnlyDisabled", "cssClass", "displayRelations", "hideRelations"].includes(attr.name) && attributeService.isAffecting(attr, this.note)
|
||||
attr.type === "label" && ["readOnly", "autoReadOnlyDisabled", "cssClass", "displayRelations", "hideRelations"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note)
|
||||
);
|
||||
|
||||
const relation = attrs.find((attr) => attr.type === "relation" && ["template", "inherit", "renderNote"].includes(attr.name) && attributeService.isAffecting(attr, this.note));
|
||||
const relation = attrs.find((attr) => attr.type === "relation" && ["template", "inherit", "renderNote"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note));
|
||||
|
||||
if (label || relation) {
|
||||
if (this.noteId && (label || relation)) {
|
||||
// probably incorrect event
|
||||
// calling this.refresh() is not enough since the event needs to be propagated to children as well
|
||||
this.triggerEvent("noteTypeMimeChanged", { noteId: this.noteId });
|
||||
@ -310,13 +339,13 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
return this.spacedUpdate.isAllSavedAndTriggerUpdate();
|
||||
}
|
||||
|
||||
readOnlyTemporarilyDisabledEvent({ noteContext }) {
|
||||
readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) {
|
||||
if (this.isNoteContext(noteContext.ntxId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async executeInActiveNoteDetailWidgetEvent({ callback }) {
|
||||
async executeInActiveNoteDetailWidgetEvent({ callback }: EventData<"executeInActiveNoteDetailWidget">) {
|
||||
if (!this.isActiveNoteContext()) {
|
||||
return;
|
||||
}
|
||||
@ -334,12 +363,15 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
// without await as this otherwise causes deadlock through component mutex
|
||||
noteCreateService.createNote(appContext.tabManager.getActiveContextNotePath(), {
|
||||
const parentNotePath = appContext.tabManager.getActiveContextNotePath();
|
||||
if (this.noteContext && parentNotePath) {
|
||||
noteCreateService.createNote(parentNotePath, {
|
||||
isProtected: note.isProtected,
|
||||
saveSelection: true,
|
||||
textEditor: await this.noteContext.getTextEditor()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// used by cutToNote in CKEditor build
|
||||
async saveNoteDetailNowCommand() {
|
||||
@ -347,12 +379,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
renderActiveNoteEvent() {
|
||||
if (this.noteContext.isActive()) {
|
||||
if (this.noteContext?.isActive()) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async executeWithTypeWidgetEvent({ resolve, ntxId }) {
|
||||
async executeWithTypeWidgetEvent({ resolve, ntxId }: EventData<"executeWithTypeWidget">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
}
|
@ -368,7 +368,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
const notePath = treeService.getNotePath(data.node);
|
||||
|
||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||
await activeNoteContext.setNote(notePath);
|
||||
const opts = {};
|
||||
if (activeNoteContext.viewScope.viewMode === "contextual-help") {
|
||||
opts.viewScope = activeNoteContext.viewScope;
|
||||
}
|
||||
await activeNoteContext.setNote(notePath, opts);
|
||||
},
|
||||
expand: (event, data) => this.setExpanded(data.node.data.branchId, true),
|
||||
collapse: (event, data) => this.setExpanded(data.node.data.branchId, false),
|
||||
@ -550,7 +554,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
$span.append($refreshSearchButton);
|
||||
}
|
||||
|
||||
if (!["search", "launcher"].includes(note.type) && !note.isOptions() && !note.isLaunchBarConfig()) {
|
||||
// TODO: Deduplicate with server's notes.ts#getAndValidateParent
|
||||
if (!["search", "launcher"].includes(note.type)
|
||||
&& !note.isOptions()
|
||||
&& !note.isLaunchBarConfig()
|
||||
&& !note.noteId.startsWith("_help")
|
||||
) {
|
||||
const $createChildNoteButton = $(`<span class="tree-item-button add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on(
|
||||
"click",
|
||||
cancelClickPropagation
|
||||
|
@ -18,8 +18,9 @@ import attributeService from "../services/attributes.js";
|
||||
import RightPanelWidget from "./right_panel_widget.js";
|
||||
import options from "../services/options.js";
|
||||
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import appContext, { type EventData } from "../components/app_context.js";
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
const TPL = `<div class="toc-widget">
|
||||
<style>
|
||||
@ -53,7 +54,16 @@ const TPL = `<div class="toc-widget">
|
||||
<span class="toc"></span>
|
||||
</div>`;
|
||||
|
||||
interface Toc {
|
||||
$toc: JQuery<HTMLElement>,
|
||||
headingCount: number
|
||||
}
|
||||
|
||||
export default class TocWidget extends RightPanelWidget {
|
||||
|
||||
private $toc!: JQuery<HTMLElement>;
|
||||
private tocLabelValue?: string | null;
|
||||
|
||||
get widgetTitle() {
|
||||
return t("toc.table_of_contents");
|
||||
}
|
||||
@ -75,7 +85,17 @@ export default class TocWidget extends RightPanelWidget {
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.note.type === "text" && !this.noteContext.viewScope.tocTemporarilyHidden && this.noteContext.viewScope.viewMode === "default";
|
||||
if (!super.isEnabled() || !this.note) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isHelpNote = (this.note.type === "doc" && this.note.noteId.startsWith("_help"));
|
||||
const isTextNote = (this.note.type === "text");
|
||||
const isNoteSupported = isTextNote || isHelpNote;
|
||||
|
||||
return isNoteSupported
|
||||
&& !this.noteContext?.viewScope?.tocTemporarilyHidden
|
||||
&& this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
async doRenderBody() {
|
||||
@ -83,36 +103,63 @@ export default class TocWidget extends RightPanelWidget {
|
||||
this.$toc = this.$body.find(".toc");
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
/*The reason for adding tocPreviousVisible is to record whether the previous state of the toc is hidden or displayed,
|
||||
* and then let it be displayed/hidden at the initial time. If there is no such value,
|
||||
* when the right panel needs to display highlighttext but not toc, every time the note content is changed,
|
||||
* toc will appear and then close immediately, because getToc(html) function will consume time*/
|
||||
this.toggleInt(!!this.noteContext.viewScope.tocPreviousVisible);
|
||||
async refreshWithNote(note: FNote) {
|
||||
|
||||
const tocLabel = note.getLabel("toc");
|
||||
this.toggleInt(!!this.noteContext?.viewScope?.tocPreviousVisible);
|
||||
|
||||
if (tocLabel?.value === "hide") {
|
||||
this.tocLabelValue = note.getLabelValue("toc");
|
||||
|
||||
if (this.tocLabelValue === "hide") {
|
||||
this.toggleInt(false);
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
return;
|
||||
}
|
||||
|
||||
let $toc = "",
|
||||
headingCount = 0;
|
||||
// Check for type text unconditionally in case alwaysShowWidget is set
|
||||
if (this.note.type === "text") {
|
||||
const { content } = await note.getBlob();
|
||||
({ $toc, headingCount } = await this.getToc(content));
|
||||
if (!this.note || !this.noteContext?.viewScope) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$toc.html($toc);
|
||||
if (["", "show"].includes(tocLabel?.value) || headingCount >= options.getInt("minTocHeadings")) {
|
||||
this.toggleInt(true);
|
||||
this.noteContext.viewScope.tocPreviousVisible = true;
|
||||
// Check for type text unconditionally in case alwaysShowWidget is set
|
||||
if (this.note.type === "text") {
|
||||
const blob = await note.getBlob();
|
||||
if (blob) {
|
||||
const toc = await this.getToc(blob.content);
|
||||
this.#updateToc(toc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.note.type === "doc") {
|
||||
/**
|
||||
* For document note types, we obtain the content directly from the DOM since it allows us to obtain processed data without
|
||||
* requesting data twice. However, when immediately navigating to a new note the new document is not yet attached to the hierarchy,
|
||||
* resulting in an empty TOC. The fix is to simply wait for it to pop up.
|
||||
*/
|
||||
setTimeout(async () => {
|
||||
const $contentEl = await this.noteContext?.getContentElement();
|
||||
if ($contentEl) {
|
||||
const content = $contentEl.html();
|
||||
const toc = await this.getToc(content);
|
||||
this.#updateToc(toc);
|
||||
} else {
|
||||
this.toggleInt(false);
|
||||
this.noteContext.viewScope.tocPreviousVisible = false;
|
||||
console.warn("Unable to get content element for doctype");
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
#updateToc({ $toc, headingCount }: Toc) {
|
||||
this.$toc.empty();
|
||||
if ($toc) {
|
||||
this.$toc.append($toc);
|
||||
}
|
||||
|
||||
const tocLabelValue = this.tocLabelValue;
|
||||
|
||||
const visible = (tocLabelValue === "" || tocLabelValue === "show") || headingCount >= (options.getInt("minTocHeadings") ?? 0);
|
||||
this.toggleInt(visible);
|
||||
if (this.noteContext?.viewScope) {
|
||||
this.noteContext.viewScope.tocPreviousVisible = visible;
|
||||
}
|
||||
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
@ -121,10 +168,10 @@ export default class TocWidget extends RightPanelWidget {
|
||||
/**
|
||||
* Rendering formulas in strings using katex
|
||||
*
|
||||
* @param {string} html Note's html content
|
||||
* @returns {string} The HTML content with mathematical formulas rendered by KaTeX.
|
||||
* @param html Note's html content
|
||||
* @returns The HTML content with mathematical formulas rendered by KaTeX.
|
||||
*/
|
||||
async replaceMathTextWithKatax(html) {
|
||||
async replaceMathTextWithKatax(html: string) {
|
||||
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
|
||||
var matches = [...html.matchAll(mathTextRegex)];
|
||||
let modifiedText = html;
|
||||
@ -167,12 +214,12 @@ export default class TocWidget extends RightPanelWidget {
|
||||
/**
|
||||
* Builds a jquery table of contents.
|
||||
*
|
||||
* @param {string} html Note's html content
|
||||
* @returns {$toc: jQuery, headingCount: integer} ordered list table of headings, nested by heading level
|
||||
* @param html Note's html content
|
||||
* @returns ordered list table of headings, nested by heading level
|
||||
* with an onclick event that will cause the document to scroll to
|
||||
* the desired position.
|
||||
*/
|
||||
async getToc(html) {
|
||||
async getToc(html: string): Promise<Toc> {
|
||||
// Regular expression for headings <h1>...</h1> using non-greedy
|
||||
// matching and backreferences
|
||||
const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi;
|
||||
@ -184,12 +231,12 @@ export default class TocWidget extends RightPanelWidget {
|
||||
// Note heading 2 is the first level Trilium makes available to the note
|
||||
let curLevel = 2;
|
||||
const $ols = [$toc];
|
||||
let headingCount;
|
||||
let headingCount = 0;
|
||||
for (let m = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) {
|
||||
//
|
||||
// Nest/unnest whatever necessary number of ordered lists
|
||||
//
|
||||
const newLevel = m[1];
|
||||
const newLevel = parseInt(m[1]);
|
||||
const levelDelta = newLevel - curLevel;
|
||||
if (levelDelta > 0) {
|
||||
// Open as many lists as newLevel - curLevel
|
||||
@ -229,7 +276,7 @@ export default class TocWidget extends RightPanelWidget {
|
||||
/**
|
||||
* Reduce indent if a larger headings are not being used: https://github.com/zadam/trilium/issues/4363
|
||||
*/
|
||||
pullLeft($toc) {
|
||||
pullLeft($toc: JQuery<HTMLElement>) {
|
||||
while (true) {
|
||||
const $children = $toc.children();
|
||||
|
||||
@ -248,16 +295,21 @@ export default class TocWidget extends RightPanelWidget {
|
||||
return $toc;
|
||||
}
|
||||
|
||||
async jumpToHeading(headingIndex) {
|
||||
async jumpToHeading(headingIndex: number) {
|
||||
if (!this.note || !this.noteContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A readonly note can change state to "readonly disabled
|
||||
// temporarily" (ie "edit this note" button) without any
|
||||
// intervening events, do the readonly calculation at navigation
|
||||
// time and not at outline creation time
|
||||
// See https://github.com/zadam/trilium/issues/2828
|
||||
const isDocNote = this.note.type === "doc";
|
||||
const isReadOnly = await this.noteContext.isReadOnly();
|
||||
|
||||
let $container;
|
||||
if (isReadOnly) {
|
||||
if (isReadOnly || isDocNote) {
|
||||
$container = await this.noteContext.getContentElement();
|
||||
} else {
|
||||
const textEditor = await this.noteContext.getTextEditor();
|
||||
@ -269,26 +321,28 @@ export default class TocWidget extends RightPanelWidget {
|
||||
}
|
||||
|
||||
async closeTocCommand() {
|
||||
if (this.noteContext?.viewScope) {
|
||||
this.noteContext.viewScope.tocTemporarilyHidden = true;
|
||||
}
|
||||
await this.refresh();
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId });
|
||||
}
|
||||
|
||||
async showTocWidgetEvent({ noteId }) {
|
||||
async showTocWidgetEvent({ noteId }: EventData<"showToc">) {
|
||||
if (this.noteId === noteId) {
|
||||
await this.refresh();
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
await this.refresh();
|
||||
} else if (
|
||||
loadResults
|
||||
.getAttributeRows()
|
||||
.find((attr) => attr.type === "label" && (attr.name.toLowerCase().includes("readonly") || attr.name === "toc") && attributeService.isAffecting(attr, this.note))
|
||||
.find((attr) => attr.type === "label" && ((attr.name ?? "").toLowerCase().includes("readonly") || attr.name === "toc") && attributeService.isAffecting(attr, this.note))
|
||||
) {
|
||||
await this.refresh();
|
||||
}
|
@ -35,6 +35,8 @@ import RibbonOptions from "./options/appearance/ribbon.js";
|
||||
import LocalizationOptions from "./options/appearance/i18n.js";
|
||||
import CodeBlockOptions from "./options/appearance/code_block.js";
|
||||
import EditorOptions from "./options/text_notes/editor.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = `<div class="note-detail-content-widget note-detail-printable">
|
||||
<style>
|
||||
@ -55,7 +57,7 @@ const TPL = `<div class="note-detail-content-widget note-detail-printable">
|
||||
<div class="note-detail-content-widget-content"></div>
|
||||
</div>`;
|
||||
|
||||
const CONTENT_WIDGETS = {
|
||||
const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
|
||||
_optionsAppearance: [LocalizationOptions, ThemeOptions, FontsOptions, CodeBlockOptions, ElectronIntegrationOptions, MaxContentWidthOptions, RibbonOptions],
|
||||
_optionsShortcuts: [KeyboardShortcutsOptions],
|
||||
_optionsTextNotes: [EditorOptions, HeadingStyleOptions, TableOfContentsOptions, HighlightsListOptions, TextAutoReadOnlySizeOptions],
|
||||
@ -81,6 +83,9 @@ const CONTENT_WIDGETS = {
|
||||
};
|
||||
|
||||
export default class ContentWidgetTypeWidget extends TypeWidget {
|
||||
|
||||
private $content!: JQuery<HTMLElement>;
|
||||
|
||||
static getType() {
|
||||
return "contentWidget";
|
||||
}
|
||||
@ -92,7 +97,7 @@ export default class ContentWidgetTypeWidget extends TypeWidget {
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
async doRefresh(note) {
|
||||
async doRefresh(note: FNote) {
|
||||
this.$content.empty();
|
||||
this.children = [];
|
||||
|
||||
@ -103,7 +108,9 @@ export default class ContentWidgetTypeWidget extends TypeWidget {
|
||||
for (const clazz of contentWidgets) {
|
||||
const widget = new clazz();
|
||||
|
||||
if (this.noteContext) {
|
||||
await widget.handleEvent("setNoteContext", { noteContext: this.noteContext });
|
||||
}
|
||||
this.child(widget);
|
||||
|
||||
this.$content.append(widget.render());
|
@ -1,4 +1,6 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
|
||||
const TPL = `<div class="note-detail-doc note-detail-printable">
|
||||
@ -13,6 +15,24 @@ const TPL = `<div class="note-detail-doc note-detail-printable">
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.note-detail-doc.contextual-help {
|
||||
padding-bottom: 15vh;
|
||||
}
|
||||
|
||||
.note-detail-doc.contextual-help h2,
|
||||
.note-detail-doc.contextual-help h3,
|
||||
.note-detail-doc.contextual-help h4,
|
||||
.note-detail-doc.contextual-help h5,
|
||||
.note-detail-doc.contextual-help h6 {
|
||||
font-size: 1.25rem;
|
||||
background-color: var(--main-background-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
margin: 0;
|
||||
padding-bottom: 0.25em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="note-detail-doc-content"></div>
|
||||
@ -34,19 +54,71 @@ export default class DocTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
const docName = note.getLabelValue("docName");
|
||||
this.initialized = this.#loadContent(note);
|
||||
this.$widget.toggleClass("contextual-help", this.noteContext?.viewScope?.viewMode === "contextual-help");
|
||||
}
|
||||
|
||||
#loadContent(note: FNote) {
|
||||
return new Promise<void>((resolve) => {
|
||||
let docName = note.getLabelValue("docName");
|
||||
|
||||
if (docName) {
|
||||
// find doc based on language
|
||||
const lng = i18next.language;
|
||||
this.$content.load(`${window.glob.appPath}/doc_notes/${lng}/${docName}.html`, (response, status) => {
|
||||
const url = this.#getUrl(docName, i18next.language);
|
||||
this.$content.load(url, (response, status) => {
|
||||
// fallback to english doc if no translation available
|
||||
if (status === "error") {
|
||||
this.$content.load(`${window.glob.appPath}/doc_notes/en/${docName}.html`);
|
||||
const fallbackUrl = this.#getUrl(docName, "en");
|
||||
this.$content.load(fallbackUrl, () => this.#processContent(fallbackUrl));
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#processContent(url);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
this.$content.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#getUrl(docNameValue: string, language: string) {
|
||||
// For help notes, we only get the content to avoid loading of styles and meta.
|
||||
let suffix = "";
|
||||
if (docNameValue?.startsWith("User Guide")) {
|
||||
suffix = " .content";
|
||||
}
|
||||
|
||||
// Cannot have spaces in the URL due to how JQuery.load works.
|
||||
docNameValue = docNameValue.replaceAll(" ", "%20");
|
||||
|
||||
return `${window.glob.appPath}/doc_notes/${language}/${docNameValue}.html${suffix}`;
|
||||
}
|
||||
|
||||
#processContent(url: string) {
|
||||
const dir = url.substring(0, url.lastIndexOf("/"));
|
||||
|
||||
// Remove top-level heading since it's already handled by the note title
|
||||
this.$content.find("h1").remove();
|
||||
|
||||
// Images are relative to the docnote but that will not work when rendered in the application since the path breaks.
|
||||
this.$content.find("img").each((i, el) => {
|
||||
const $img = $(el);
|
||||
$img.attr("src", dir + "/" + $img.attr("src"));
|
||||
});
|
||||
|
||||
applySyntaxHighlight(this.$content);
|
||||
}
|
||||
|
||||
async executeWithContentElementEvent({ resolve, ntxId }: EventData<"executeWithContentElement">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.initialized;
|
||||
|
||||
resolve(this.$content);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import type SpacedUpdate from "../../services/spaced_update.js";
|
||||
|
||||
export default abstract class TypeWidget extends NoteContextAwareWidget {
|
||||
|
||||
protected spacedUpdate!: SpacedUpdate;
|
||||
spacedUpdate!: SpacedUpdate;
|
||||
|
||||
// for overriding
|
||||
static getType() {}
|
||||
@ -45,6 +45,14 @@ export default abstract class TypeWidget extends NoteContextAwareWidget {
|
||||
|
||||
focus() {}
|
||||
|
||||
scrollToEnd() {
|
||||
// Do nothing by default.
|
||||
}
|
||||
|
||||
dataSaved() {
|
||||
// Do nothing by default.
|
||||
}
|
||||
|
||||
async readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) {
|
||||
if (this.isNoteContext(noteContext.ntxId)) {
|
||||
await this.refresh();
|
||||
|
@ -1606,3 +1606,9 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
|
||||
background: var(--hover-item-background-color);
|
||||
color: var(--hover-item-text-color);
|
||||
}
|
||||
|
||||
.note-split.type-geoMap .floating-buttons-children {
|
||||
background: var(--main-background-color);
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
|
||||
border-radius: 4px;
|
||||
}
|
@ -641,7 +641,8 @@
|
||||
"show_hidden_subtree": "Show Hidden Subtree",
|
||||
"show_help": "Show Help",
|
||||
"about": "About TriliumNext Notes",
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"show-cheatsheet": "Show Cheatsheet"
|
||||
},
|
||||
"sync_status": {
|
||||
"unknown": "<p>Sync status will be known once the next sync attempt starts.</p><p>Click to trigger sync now.</p>",
|
||||
@ -1644,5 +1645,8 @@
|
||||
"geo-map-context": {
|
||||
"open-location": "Open location",
|
||||
"remove-from-map": "Remove from map"
|
||||
},
|
||||
"help-button": {
|
||||
"title": "Open the relevant help page"
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import type AttributeMeta from "../meta/attribute_meta.js";
|
||||
import type BBranch from "../../becca/entities/bbranch.js";
|
||||
import type { Response } from "express";
|
||||
import { RESOURCE_DIR } from "../resource_dir.js";
|
||||
import type { NoteMetaFile } from "../meta/note_meta.js";
|
||||
|
||||
async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response | fs.WriteStream, setHeaders = true) {
|
||||
if (!["html", "markdown"].includes(format)) {
|
||||
@ -485,8 +486,11 @@ ${markdownContent}`;
|
||||
|
||||
const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {};
|
||||
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
|
||||
if (!rootMeta) {
|
||||
throw new Error("Unable to create root meta.");
|
||||
}
|
||||
|
||||
const metaFile = {
|
||||
const metaFile: NoteMetaFile = {
|
||||
formatVersion: 2,
|
||||
appVersion: packageInfo.version,
|
||||
files: [rootMeta]
|
||||
|
@ -6,6 +6,8 @@ import noteService from "./notes.js";
|
||||
import log from "./log.js";
|
||||
import migrationService from "./migration.js";
|
||||
import { t } from "i18next";
|
||||
import app_path from "./app_path.js";
|
||||
import { getHelpHiddenSubtreeData } from "./in_app_help.js";
|
||||
|
||||
const LBTPL_ROOT = "_lbTplRoot";
|
||||
const LBTPL_BASE = "_lbTplBase";
|
||||
@ -16,21 +18,21 @@ const LBTPL_BUILTIN_WIDGET = "_lbTplBuiltinWidget";
|
||||
const LBTPL_SPACER = "_lbTplSpacer";
|
||||
const LBTPL_CUSTOM_WIDGET = "_lbTplCustomWidget";
|
||||
|
||||
interface Attribute {
|
||||
interface HiddenSubtreeAttribute {
|
||||
type: AttributeType;
|
||||
name: string;
|
||||
isInheritable?: boolean;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
interface Item {
|
||||
export interface HiddenSubtreeItem {
|
||||
notePosition?: number;
|
||||
id: string;
|
||||
title: string;
|
||||
type: NoteType;
|
||||
icon?: string;
|
||||
attributes?: Attribute[];
|
||||
children?: Item[];
|
||||
attributes?: HiddenSubtreeAttribute[];
|
||||
children?: HiddenSubtreeItem[];
|
||||
isExpanded?: boolean;
|
||||
baseSize?: string;
|
||||
growthFactor?: string;
|
||||
@ -54,9 +56,9 @@ enum Command {
|
||||
* duplicate subtrees. This way, all instances will generate the same structure with the same IDs.
|
||||
*/
|
||||
|
||||
let hiddenSubtreeDefinition: Item;
|
||||
let hiddenSubtreeDefinition: HiddenSubtreeItem;
|
||||
|
||||
function buildHiddenSubtreeDefinition(): Item {
|
||||
function buildHiddenSubtreeDefinition(): HiddenSubtreeItem {
|
||||
return {
|
||||
id: "_hidden",
|
||||
title: t("hidden-subtree.root-title"),
|
||||
@ -345,6 +347,14 @@ function buildHiddenSubtreeDefinition(): Item {
|
||||
{ id: "_optionsOther", title: t("hidden-subtree.other"), type: "contentWidget", icon: "bx-dots-horizontal" },
|
||||
{ id: "_optionsAdvanced", title: t("hidden-subtree.advanced-title"), type: "contentWidget" }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "_help",
|
||||
title: t("hidden-subtree.user-guide"),
|
||||
type: "book",
|
||||
icon: "bx-help-circle",
|
||||
children: getHelpHiddenSubtreeData(),
|
||||
isExpanded: true
|
||||
}
|
||||
]
|
||||
};
|
||||
@ -368,7 +378,7 @@ function checkHiddenSubtree(force = false, extraOpts: CheckHiddenExtraOpts = {})
|
||||
checkHiddenSubtreeRecursively("root", hiddenSubtreeDefinition, extraOpts);
|
||||
}
|
||||
|
||||
function checkHiddenSubtreeRecursively(parentNoteId: string, item: Item, extraOpts: CheckHiddenExtraOpts = {}) {
|
||||
function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtreeItem, extraOpts: CheckHiddenExtraOpts = {}) {
|
||||
if (!item.id || !item.type || !item.title) {
|
||||
throw new Error(`Item does not contain mandatory properties: ${JSON.stringify(item)}`);
|
||||
}
|
||||
@ -449,7 +459,9 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: Item, extraOp
|
||||
for (const attr of attrs) {
|
||||
const attrId = note.noteId + "_" + attr.type.charAt(0) + attr.name;
|
||||
|
||||
if (!note.getAttributes().find((attr) => attr.attributeId === attrId)) {
|
||||
const existingAttribute = note.getAttributes().find((attr) => attr.attributeId === attrId);
|
||||
|
||||
if (!existingAttribute) {
|
||||
new BAttribute({
|
||||
attributeId: attrId,
|
||||
noteId: note.noteId,
|
||||
@ -458,6 +470,10 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: Item, extraOp
|
||||
value: attr.value,
|
||||
isInheritable: false
|
||||
}).save();
|
||||
} else if (attr.name === "docName") {
|
||||
// Updating docname
|
||||
existingAttribute.value = attr.value ?? "";
|
||||
existingAttribute.save();
|
||||
}
|
||||
}
|
||||
|
||||
|
87
src/services/in_app_help.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import type { HiddenSubtreeItem } from "./hidden_subtree.js";
|
||||
import type NoteMeta from "./meta/note_meta.js";
|
||||
import type { NoteMetaFile } from "./meta/note_meta.js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { isDev } from "./utils.js";
|
||||
|
||||
export function getHelpHiddenSubtreeData() {
|
||||
const srcRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const appDir = path.join(srcRoot, "public", isDev ? "app" : "app-dist");
|
||||
const helpDir = path.join(appDir, "doc_notes", "en", "User Guide");
|
||||
const metaFilePath = path.join(helpDir, "!!!meta.json");
|
||||
const metaFileContent = JSON.parse(fs.readFileSync(metaFilePath).toString("utf-8"));
|
||||
|
||||
try {
|
||||
return parseNoteMetaFile(metaFileContent as NoteMetaFile);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function parseNoteMetaFile(noteMetaFile: NoteMetaFile): HiddenSubtreeItem[] {
|
||||
if (!noteMetaFile.files) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const metaRoot = noteMetaFile.files[0];
|
||||
const parsedMetaRoot = parseNoteMeta(metaRoot, "/" + (metaRoot.dirFileName ?? ""));
|
||||
console.log(JSON.stringify(parsedMetaRoot, null, 4));
|
||||
return parsedMetaRoot.children ?? [];
|
||||
}
|
||||
|
||||
function parseNoteMeta(noteMeta: NoteMeta, docNameRoot: string): HiddenSubtreeItem {
|
||||
let iconClass: string = "bx bx-file";
|
||||
const item: HiddenSubtreeItem = {
|
||||
id: `_help_${noteMeta.noteId}`,
|
||||
title: noteMeta.title ?? "",
|
||||
type: "doc", // can change
|
||||
attributes: []
|
||||
};
|
||||
|
||||
// Handle attributes
|
||||
for (const attribute of noteMeta.attributes ?? []) {
|
||||
if (attribute.name === "iconClass") {
|
||||
iconClass = attribute.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle folder notes
|
||||
if (!noteMeta.dataFileName) {
|
||||
iconClass = "bx bx-folder";
|
||||
item.type = "book";
|
||||
}
|
||||
|
||||
// Handle text notes
|
||||
if (noteMeta.type === "text" && noteMeta.dataFileName) {
|
||||
const docPath = `${docNameRoot}/${path.basename(noteMeta.dataFileName, ".html")}`
|
||||
.substring(1);
|
||||
item.attributes?.push({
|
||||
type: "label",
|
||||
name: "docName",
|
||||
value: docPath
|
||||
});
|
||||
}
|
||||
|
||||
// Handle children
|
||||
if (noteMeta.children) {
|
||||
const children: HiddenSubtreeItem[] = [];
|
||||
for (const childMeta of noteMeta.children) {
|
||||
let newDocNameRoot = (noteMeta.dirFileName ? `${docNameRoot}/${noteMeta.dirFileName}` : docNameRoot);
|
||||
children.push(parseNoteMeta(childMeta, newDocNameRoot));
|
||||
}
|
||||
|
||||
item.children = children;
|
||||
}
|
||||
|
||||
// Handle note icon
|
||||
item.attributes?.push({
|
||||
name: "iconClass",
|
||||
value: iconClass,
|
||||
type: "label"
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
@ -344,6 +344,12 @@ function getDefaultKeyboardActions() {
|
||||
description: t("keyboard_actions.show-help"),
|
||||
scope: "window"
|
||||
},
|
||||
{
|
||||
actionName: "showCheatsheet",
|
||||
defaultShortcuts: ["Shift+F1"],
|
||||
description: t("keyboard_actions.show-cheatsheet"),
|
||||
scope: "window"
|
||||
},
|
||||
|
||||
{
|
||||
separator: t("keyboard_actions.text-note-operations")
|
||||
|
@ -51,6 +51,7 @@ const enum KeyboardActionNamesEnum {
|
||||
showRecentChanges,
|
||||
showSQLConsole,
|
||||
showBackendLog,
|
||||
showCheatsheet,
|
||||
showHelp,
|
||||
addLinkToText,
|
||||
followLinkUnderCursor,
|
||||
|
@ -1,6 +1,12 @@
|
||||
import type AttachmentMeta from "./attachment_meta.js";
|
||||
import type AttributeMeta from "./attribute_meta.js";
|
||||
|
||||
export interface NoteMetaFile {
|
||||
formatVersion: number;
|
||||
appVersion: string;
|
||||
files: NoteMeta[];
|
||||
}
|
||||
|
||||
export default interface NoteMeta {
|
||||
noteId?: string;
|
||||
notePath?: string[];
|
||||
|
@ -146,7 +146,10 @@ function getAndValidateParent(params: GetValidateParams) {
|
||||
}
|
||||
|
||||
if (!params.ignoreForbiddenParents) {
|
||||
if (["_lbRoot", "_hidden"].includes(parentNote.noteId) || parentNote.noteId.startsWith("_lbTpl") || parentNote.isOptions()) {
|
||||
if (["_lbRoot", "_hidden"].includes(parentNote.noteId)
|
||||
|| parentNote.noteId.startsWith("_lbTpl")
|
||||
|| parentNote.noteId.startsWith("_help")
|
||||
|| parentNote.isOptions()) {
|
||||
throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
|
||||
}
|
||||
}
|
||||
|
@ -88,10 +88,11 @@
|
||||
"reset-zoom-level": "Reset zoom level",
|
||||
"copy-without-formatting": "Copy selected text without formatting",
|
||||
"force-save-revision": "Force creating / saving new note revision of the active note",
|
||||
"show-help": "Shows built-in Help / cheatsheet",
|
||||
"show-help": "Shows the built-in User Guide",
|
||||
"toggle-book-properties": "Toggle Book Properties",
|
||||
"toggle-classic-editor-toolbar": "Toggle the Formatting tab for the editor with fixed toolbar",
|
||||
"export-as-pdf": "Exports the current note as a PDF"
|
||||
"export-as-pdf": "Exports the current note as a PDF",
|
||||
"show-cheatsheet": "Shows a modal with common keyboard operations"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login",
|
||||
@ -241,7 +242,8 @@
|
||||
"sync-title": "Sync",
|
||||
"other": "Other",
|
||||
"advanced-title": "Advanced",
|
||||
"visible-launchers-title": "Visible Launchers"
|
||||
"visible-launchers-title": "Visible Launchers",
|
||||
"user-guide": "User Guide"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "New note",
|
||||
|