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(); |     await app.goto(); | ||||||
|  |  | ||||||
|     const popupPromise = page.waitForEvent("popup"); |     const popupPromise = page.waitForEvent("popup"); | ||||||
|     await app.currentNoteSplit.press("F1"); |     await app.currentNoteSplit.press("Shift+F1"); | ||||||
|     await page.getByRole("link", { name: "online" }).click(); |     await page.getByRole("link", { name: "online" }).click(); | ||||||
|     const popup = await popupPromise; |     const popup = await popupPromise; | ||||||
|     expect(popup.url()).toBe("https://triliumnext.github.io/Docs/"); |     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 NoteTreeWidget from "../widgets/note_tree.js"; | ||||||
| import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | ||||||
| import type { ContextMenuEvent } from "../menus/context_menu.js"; | import type { ContextMenuEvent } from "../menus/context_menu.js"; | ||||||
|  | import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | ||||||
|  |  | ||||||
| interface Layout { | interface Layout { | ||||||
|     getRootWidget: (appContext: AppContext) => RootWidget; |     getRootWidget: (appContext: AppContext) => RootWidget; | ||||||
| @@ -61,8 +62,8 @@ export interface NoteCommandData extends CommandData { | |||||||
|     viewScope?: ViewScope; |     viewScope?: ViewScope; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface ExecuteCommandData extends CommandData { | export interface ExecuteCommandData<T> extends CommandData { | ||||||
|     resolve: unknown; |     resolve: (data: T) => void | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -77,6 +78,7 @@ export type CommandMappings = { | |||||||
|         searchString?: string; |         searchString?: string; | ||||||
|         ancestorNoteId?: string | null; |         ancestorNoteId?: string | null; | ||||||
|     }; |     }; | ||||||
|  |     closeTocCommand: CommandData; | ||||||
|     showLaunchBarSubtree: CommandData; |     showLaunchBarSubtree: CommandData; | ||||||
|     showOptions: CommandData & { |     showOptions: CommandData & { | ||||||
|         section: string; |         section: string; | ||||||
| @@ -151,12 +153,16 @@ export type CommandMappings = { | |||||||
|         callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void; |         callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void; | ||||||
|     }; |     }; | ||||||
|     executeWithTextEditor: CommandData & |     executeWithTextEditor: CommandData & | ||||||
|         ExecuteCommandData & { |         ExecuteCommandData<TextEditor> & { | ||||||
|             callback?: GetTextEditorCallback; |             callback?: GetTextEditorCallback; | ||||||
|         }; |         }; | ||||||
|     executeWithCodeEditor: CommandData & ExecuteCommandData; |     executeWithCodeEditor: CommandData & ExecuteCommandData<null>; | ||||||
|     executeWithContentElement: CommandData & ExecuteCommandData; |     /** | ||||||
|     executeWithTypeWidget: CommandData & ExecuteCommandData; |      * 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 & { |     addTextToActiveEditor: CommandData & { | ||||||
|         text: string; |         text: string; | ||||||
|     }; |     }; | ||||||
| @@ -202,6 +208,9 @@ export type CommandMappings = { | |||||||
|         zoomFactor: string; |         zoomFactor: string; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     reEvaluateRightPaneVisibility: CommandData; | ||||||
|  |     runActiveNote: CommandData; | ||||||
|  |  | ||||||
|     // Geomap |     // Geomap | ||||||
|     deleteFromMap: { noteId: string }, |     deleteFromMap: { noteId: string }, | ||||||
|     openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent } |     openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent } | ||||||
| @@ -247,7 +256,7 @@ type EventMappings = { | |||||||
|     }; |     }; | ||||||
|     noteSwitched: { |     noteSwitched: { | ||||||
|         noteContext: NoteContext; |         noteContext: NoteContext; | ||||||
|         notePath: string | null; |         notePath?: string | null; | ||||||
|     }; |     }; | ||||||
|     noteSwitchedAndActivatedEvent: { |     noteSwitchedAndActivatedEvent: { | ||||||
|         noteContext: NoteContext; |         noteContext: NoteContext; | ||||||
| @@ -262,6 +271,9 @@ type EventMappings = { | |||||||
|     reEvaluateHighlightsListWidgetVisibility: { |     reEvaluateHighlightsListWidgetVisibility: { | ||||||
|         noteId: string | undefined; |         noteId: string | undefined; | ||||||
|     }; |     }; | ||||||
|  |     reEvaluateTocWidgetVisibility: { | ||||||
|  |         noteId: string | undefined; | ||||||
|  |     }; | ||||||
|     showHighlightsListWidget: { |     showHighlightsListWidget: { | ||||||
|         noteId: string; |         noteId: string; | ||||||
|     }; |     }; | ||||||
| @@ -297,7 +309,12 @@ type EventMappings = { | |||||||
|     }; |     }; | ||||||
|     refreshNoteList: { |     refreshNoteList: { | ||||||
|         noteId: string; |         noteId: string; | ||||||
|     } |     }; | ||||||
|  |     showToc: { | ||||||
|  |         noteId: string; | ||||||
|  |     }; | ||||||
|  |     scrollToEnd: { ntxId: string }; | ||||||
|  |     noteTypeMimeChanged: { noteId: string }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export type EventListener<T extends EventNames> = { | export type EventListener<T extends EventNames> = { | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import hoistedNoteService from "../services/hoisted_note.js"; | |||||||
| import options from "../services/options.js"; | import options from "../services/options.js"; | ||||||
| import type { ViewScope } from "../services/link.js"; | import type { ViewScope } from "../services/link.js"; | ||||||
| import type FNote from "../entities/fnote.js"; | import type FNote from "../entities/fnote.js"; | ||||||
|  | import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | ||||||
|  |  | ||||||
| interface SetNoteOpts { | interface SetNoteOpts { | ||||||
|     triggerSwitchEvent?: unknown; |     triggerSwitchEvent?: unknown; | ||||||
| @@ -288,7 +289,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> | |||||||
|     hasNoteList() { |     hasNoteList() { | ||||||
|         return ( |         return ( | ||||||
|             this.note && |             this.note && | ||||||
|             this.viewScope?.viewMode === "default" && |             ["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "") && | ||||||
|             this.note.hasChildren() && |             this.note.hasChildren() && | ||||||
|             ["book", "text", "code"].includes(this.note.type) && |             ["book", "text", "code"].includes(this.note.type) && | ||||||
|             this.note.mime !== "text/x-sqlite;schema=trilium" && |             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() { |     async getContentElement() { | ||||||
|         return this.timeout<JQuery<HTMLElement>>( |         return this.timeout<JQuery<HTMLElement>>( | ||||||
|             new Promise((resolve) => |             new Promise((resolve) => | ||||||
| @@ -332,7 +342,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> | |||||||
|  |  | ||||||
|     async getTypeWidget() { |     async getTypeWidget() { | ||||||
|         return this.timeout( |         return this.timeout( | ||||||
|             new Promise((resolve) => |             new Promise<TypeWidget | null>((resolve) => | ||||||
|                 appContext.triggerCommand("executeWithTypeWidget", { |                 appContext.triggerCommand("executeWithTypeWidget", { | ||||||
|                     resolve, |                     resolve, | ||||||
|                     ntxId: this.ntxId |                     ntxId: this.ntxId | ||||||
|   | |||||||
| @@ -90,6 +90,10 @@ export default class RootCommandExecutor extends Component { | |||||||
|         await appContext.tabManager.openTabWithNoteWithHoisting("_backendLog", { activate: true }); |         await appContext.tabManager.openTabWithNoteWithHoisting("_backendLog", { activate: true }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async showHelpCommand() { | ||||||
|  |         await this.showAndHoistSubtree("_help"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async showLaunchBarSubtreeCommand() { |     async showLaunchBarSubtreeCommand() { | ||||||
|         const rootNote = utils.isMobile() ? "_lbMobileRoot" : "_lbRoot"; |         const rootNote = utils.isMobile() ? "_lbMobileRoot" : "_lbRoot"; | ||||||
|         await this.showAndHoistSubtree(rootNote); |         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 options from "../services/options.js"; | ||||||
| import utils from "../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
| import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js"; | import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js"; | ||||||
|  | import ContextualHelpButton from "../widgets/floating_buttons/help_button.js"; | ||||||
|  |  | ||||||
| export default class DesktopLayout { | export default class DesktopLayout { | ||||||
|     constructor(customWidgets) { |     constructor(customWidgets) { | ||||||
| @@ -205,6 +206,7 @@ export default class DesktopLayout { | |||||||
|                                                                 .child(new CopyImageReferenceButton()) |                                                                 .child(new CopyImageReferenceButton()) | ||||||
|                                                                 .child(new SvgExportButton()) |                                                                 .child(new SvgExportButton()) | ||||||
|                                                                 .child(new BacklinksWidget()) |                                                                 .child(new BacklinksWidget()) | ||||||
|  |                                                                 .child(new ContextualHelpButton()) | ||||||
|                                                                 .child(new HideFloatingButtonsButton()) |                                                                 .child(new HideFloatingButtonsButton()) | ||||||
|                                                         ) |                                                         ) | ||||||
|                                                         .child(new MermaidWidget()) |                                                         .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 noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node); | ||||||
|  |  | ||||||
|         const notSearch = note?.type !== "search"; |         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 parentNotSearch = !parentNote || parentNote.type !== "search"; | ||||||
|         const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; |         const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; | ||||||
|  |  | ||||||
| @@ -80,7 +80,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree | |||||||
|                 command: "insertNoteAfter", |                 command: "insertNoteAfter", | ||||||
|                 uiIcon: "bx bx-plus", |                 uiIcon: "bx bx-plus", | ||||||
|                 items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null, |                 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", |                 command: "insertChildNote", | ||||||
|                 uiIcon: "bx bx-plus", |                 uiIcon: "bx bx-plus", | ||||||
|                 items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null, |                 items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null, | ||||||
|                 enabled: notSearch && noSelectedNotes && notOptions |                 enabled: notSearch && noSelectedNotes && notOptionsOrHelp | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|             { title: "----" }, |             { 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>`, |                         title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`, | ||||||
|                         command: "editBranchPrefix", |                         command: "editBranchPrefix", | ||||||
|                         uiIcon: "bx bx-rename", |                         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">`, |                         title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`, | ||||||
|                         command: "duplicateSubtree", |                         command: "duplicateSubtree", | ||||||
|                         uiIcon: "bx bx-outline", |                         uiIcon: "bx bx-outline", | ||||||
|                         enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions |                         enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp | ||||||
|                     }, |                     }, | ||||||
|  |  | ||||||
|                     { title: "----" }, |                     { title: "----" }, | ||||||
| @@ -136,7 +136,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree | |||||||
|                     { title: "----" }, |                     { title: "----" }, | ||||||
|  |  | ||||||
|                     { title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true }, |                     { 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>`, |                 title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`, | ||||||
|                 command: "deleteNotes", |                 command: "deleteNotes", | ||||||
|                 uiIcon: "bx bx-trash destructive-action-icon", |                 uiIcon: "bx bx-trash destructive-action-icon", | ||||||
|                 enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions |                 enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|             { title: "----" }, |             { 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: "----" }, |             { title: "----" }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ async function renderAttributes(attributes: FAttribute[], renderIsInheritable: b | |||||||
|     return $container; |     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) { | async function renderNormalAttributes(note: FNote) { | ||||||
|     const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes(); |     const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes(); | ||||||
|   | |||||||
| @@ -25,14 +25,29 @@ async function getLinkIcon(noteId: string, viewMode: ViewMode | undefined) { | |||||||
|     return icon; |     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 { | 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; |     viewMode?: ViewMode; | ||||||
|     attachmentId?: string; |     attachmentId?: string; | ||||||
|     readOnlyTemporarilyDisabled?: boolean; |     readOnlyTemporarilyDisabled?: boolean; | ||||||
|     highlightsListPreviousVisible?: boolean; |     highlightsListPreviousVisible?: boolean; | ||||||
|     highlightsListTemporarilyHidden?: 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 { | interface CreateLinkOptions { | ||||||
|   | |||||||
| @@ -22,11 +22,7 @@ interface CreateNoteOpts { | |||||||
|     focus?: "title" | "content"; |     focus?: "title" | "content"; | ||||||
|     target?: string; |     target?: string; | ||||||
|     targetBranchId?: string; |     targetBranchId?: string; | ||||||
|     textEditor?: { |     textEditor?: TextEditor; | ||||||
|         // TODO: Replace with interface once note_context.js is converted. |  | ||||||
|         getSelectedHtml(): string; |  | ||||||
|         removeSelection(): void; |  | ||||||
|     }; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| interface Response { | interface Response { | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -239,6 +239,9 @@ declare global { | |||||||
|         }, |         }, | ||||||
|         getData(): string; |         getData(): string; | ||||||
|         setData(data: string): void; |         setData(data: string): void; | ||||||
|  |         getSelectedHtml(): string; | ||||||
|  |         removeSelection(): void; | ||||||
|  |         sourceElement: HTMLElement; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     interface MentionItem { |     interface MentionItem { | ||||||
|   | |||||||
| @@ -226,6 +226,12 @@ const TPL = ` | |||||||
|             <kbd data-command="showHelp"></kbd> |             <kbd data-command="showHelp"></kbd> | ||||||
|         </li> |         </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"> |         <li class="dropdown-item show-about-dialog-button"> | ||||||
|             <span class="bx bx-info-circle"></span> |             <span class="bx bx-info-circle"></span> | ||||||
|             ${t("global_menu.about")} |             ${t("global_menu.about")} | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         await appContext.tabManager.activateNoteContext(noteContext.ntxId); |         await appContext.tabManager.activateNoteContext(noteContext.ntxId); | ||||||
|  |  | ||||||
|         if (notePath) { |         if (notePath) { | ||||||
|             await noteContext.setNote(notePath, viewScope); |             await noteContext.setNote(notePath, { viewScope }); | ||||||
|         } else { |         } else { | ||||||
|             await noteContext.setEmpty(); |             await noteContext.setEmpty(); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -153,7 +153,7 @@ export default class HelpDialog extends BasicWidget { | |||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     showHelpEvent() { |     showCheatsheetEvent() { | ||||||
|         utils.openDialog(this.$widget); |         utils.openDialog(this.$widget); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ const TPL = ` | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         .floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) { |         .floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) { | ||||||
|             margin-left: 10px; |             margin: 2px; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .floating-buttons-children > button, .floating-buttons-children .floating-button { |         .floating-buttons-children > button, .floating-buttons-children .floating-button { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ const TPL = `\ | |||||||
| <div class="geo-map-buttons"> | <div class="geo-map-buttons"> | ||||||
|     <style> |     <style> | ||||||
|         .geo-map-buttons { |         .geo-map-buttons { | ||||||
|  |             contain: none; | ||||||
|             display: flex; |             display: flex; | ||||||
|             gap: 10px; |             gap: 10px; | ||||||
|         } |         } | ||||||
| @@ -12,13 +13,6 @@ const TPL = `\ | |||||||
|         .leaflet-pane { |         .leaflet-pane { | ||||||
|             z-index: 50; |             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> |     </style> | ||||||
|  |  | ||||||
|     <button type="button" |     <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 SpacedUpdate from "../services/spaced_update.js"; | ||||||
| import server from "../services/server.js"; | import server from "../services/server.js"; | ||||||
| import libraryLoader from "../services/library_loader.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 keyboardActionsService from "../services/keyboard_actions.js"; | ||||||
| import noteCreateService from "../services/note_create.js"; | import noteCreateService from "../services/note_create.js"; | ||||||
| import attributeService from "../services/attributes.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 { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js"; | ||||||
| import GeoMapTypeWidget from "./type_widgets/geo_map.js"; | import GeoMapTypeWidget from "./type_widgets/geo_map.js"; | ||||||
| import utils from "../services/utils.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 = ` | const TPL = ` | ||||||
| <div class="note-detail"> | <div class="note-detail"> | ||||||
| @@ -73,14 +75,34 @@ const typeWidgetClasses = { | |||||||
|     geoMap: GeoMapTypeWidget |     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 { | export default class NoteDetailWidget extends NoteContextAwareWidget { | ||||||
|  | 
 | ||||||
|  |     private typeWidgets: Record<string, TypeWidget>; | ||||||
|  |     private spacedUpdate: SpacedUpdate; | ||||||
|  |     private type?: ExtendedNoteType; | ||||||
|  |     private mime?: string; | ||||||
|  | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         this.typeWidgets = {}; |         this.typeWidgets = {}; | ||||||
| 
 | 
 | ||||||
|         this.spacedUpdate = new SpacedUpdate(async () => { |         this.spacedUpdate = new SpacedUpdate(async () => { | ||||||
|  |             if (!this.noteContext) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             const { note } = this.noteContext; |             const { note } = this.noteContext; | ||||||
|  |             if (!note) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             const { noteId } = note; |             const { noteId } = note; | ||||||
| 
 | 
 | ||||||
|             const data = await this.getTypeWidget().getData(); |             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); |             await server.put(`notes/${noteId}/data`, data, this.componentId); | ||||||
| 
 | 
 | ||||||
|             this.getTypeWidget().dataSaved?.(); |             this.getTypeWidget().dataSaved(); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         appContext.addBeforeUnloadListener(this); |         appContext.addBeforeUnloadListener(this); | ||||||
| @@ -129,13 +151,17 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
| 
 | 
 | ||||||
|             this.$widget.append($renderedWidget); |             this.$widget.append($renderedWidget); | ||||||
| 
 | 
 | ||||||
|  |             if (this.noteContext) { | ||||||
|                 await typeWidget.handleEvent("setNoteContext", { noteContext: 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
 |             // this is happening in update(), so note has been already set, and we need to reflect this
 | ||||||
|  |             if (this.noteContext) { | ||||||
|                 await typeWidget.handleEvent("noteSwitched", { |                 await typeWidget.handleEvent("noteSwitched", { | ||||||
|                     noteContext: this.noteContext, |                     noteContext: this.noteContext, | ||||||
|                     notePath: this.noteContext.notePath |                     notePath: this.noteContext.notePath | ||||||
|                 }); |                 }); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             this.child(typeWidget); |             this.child(typeWidget); | ||||||
|         } |         } | ||||||
| @@ -150,57 +176,60 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|         // https://github.com/zadam/trilium/issues/2522
 |         // https://github.com/zadam/trilium/issues/2522
 | ||||||
|         const isBackendNote = this.noteContext?.noteId === "_backendLog"; |         const isBackendNote = this.noteContext?.noteId === "_backendLog"; | ||||||
|         const isSqlNote = this.mime === "text/x-sqlite;schema=trilium"; |         const isSqlNote = this.mime === "text/x-sqlite;schema=trilium"; | ||||||
|         const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "geoMap"].includes(this.type); |         const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "geoMap"].includes(this.type ?? ""); | ||||||
|         const isFullHeight = (!this.noteContext.hasNoteList() && isFullHeightNoteType && !isSqlNote) |         const isFullHeight = (!this.noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote) | ||||||
|             || this.noteContext.viewScope.viewMode === "attachments" |             || this.noteContext?.viewScope?.viewMode === "attachments" | ||||||
|             || isBackendNote; |             || isBackendNote; | ||||||
| 
 | 
 | ||||||
|         this.$widget.toggleClass("full-height", isFullHeight); |         this.$widget.toggleClass("full-height", isFullHeight); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getTypeWidget() { |     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 })); |             throw new Error(t(`note_detail.could_not_find_typewidget`, { type: this.type })); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.typeWidgets[this.type]; |         return this.typeWidgets[this.type]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getWidgetType() { |     async getWidgetType(): Promise<ExtendedNoteType> { | ||||||
|         const note = this.note; |         const note = this.note; | ||||||
| 
 | 
 | ||||||
|         if (!note) { |         if (!note) { | ||||||
|             return "empty"; |             return "empty"; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let type = note.type; |         let type: NoteType = note.type; | ||||||
|         const viewScope = this.noteContext.viewScope; |         let resultingType: ExtendedNoteType; | ||||||
|  |         const viewScope = this.noteContext?.viewScope; | ||||||
| 
 | 
 | ||||||
|         if (viewScope.viewMode === "source") { |         if (viewScope?.viewMode === "source") { | ||||||
|             type = "readOnlyCode"; |             resultingType = "readOnlyCode"; | ||||||
|         } else if (viewScope.viewMode === "attachments") { |         } else if (viewScope && viewScope.viewMode === "attachments") { | ||||||
|             type = viewScope.attachmentId ? "attachmentDetail" : "attachmentList"; |             resultingType = viewScope.attachmentId ? "attachmentDetail" : "attachmentList"; | ||||||
|         } else if (type === "text" && (await this.noteContext.isReadOnly())) { |         } else if (type === "text" && (await this.noteContext?.isReadOnly())) { | ||||||
|             type = "readOnlyText"; |             resultingType = "readOnlyText"; | ||||||
|         } else if ((type === "code" || type === "mermaid") && (await this.noteContext.isReadOnly())) { |         } else if ((type === "code" || type === "mermaid") && (await this.noteContext?.isReadOnly())) { | ||||||
|             type = "readOnlyCode"; |             resultingType = "readOnlyCode"; | ||||||
|         } else if (type === "text") { |         } else if (type === "text") { | ||||||
|             type = "editableText"; |             resultingType = "editableText"; | ||||||
|         } else if (type === "code" || type === "mermaid") { |         } else if (type === "code" || type === "mermaid") { | ||||||
|             type = "editableCode"; |             resultingType = "editableCode"; | ||||||
|         } else if (type === "launcher") { |         } else if (type === "launcher") { | ||||||
|             type = "doc"; |             resultingType = "doc"; | ||||||
|  |         } else { | ||||||
|  |             resultingType = type; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { |         if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { | ||||||
|             type = "protectedSession"; |             resultingType = "protectedSession"; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return type; |         return resultingType; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async focusOnDetailEvent({ ntxId }) { |     async focusOnDetailEvent({ ntxId }: EventData<"focusOnDetail">) { | ||||||
|         if (this.noteContext.ntxId !== ntxId) { |         if (this.noteContext?.ntxId !== ntxId) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -210,8 +239,8 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|         widget.focus(); |         widget.focus(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async scrollToEndEvent({ ntxId }) { |     async scrollToEndEvent({ ntxId }: EventData<"scrollToEnd">) { | ||||||
|         if (this.noteContext.ntxId !== ntxId) { |         if (this.noteContext?.ntxId !== ntxId) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -224,29 +253,29 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async beforeNoteSwitchEvent({ noteContext }) { |     async beforeNoteSwitchEvent({ noteContext }: EventData<"beforeNoteSwitch">) { | ||||||
|         if (this.isNoteContext(noteContext.ntxId)) { |         if (this.isNoteContext(noteContext.ntxId)) { | ||||||
|             await this.spacedUpdate.updateNowIfNecessary(); |             await this.spacedUpdate.updateNowIfNecessary(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async beforeNoteContextRemoveEvent({ ntxIds }) { |     async beforeNoteContextRemoveEvent({ ntxIds }: EventData<"beforeNoteContextRemove">) { | ||||||
|         if (this.isNoteContext(ntxIds)) { |         if (this.isNoteContext(ntxIds)) { | ||||||
|             await this.spacedUpdate.updateNowIfNecessary(); |             await this.spacedUpdate.updateNowIfNecessary(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async runActiveNoteCommand(params) { |     async runActiveNoteCommand(params: CommandListenerData<"runActiveNote">) { | ||||||
|         if (this.isNoteContext(params.ntxId)) { |         if (this.isNoteContext(params.ntxId)) { | ||||||
|             // make sure that script is saved before running it #4028
 |             // make sure that script is saved before running it #4028
 | ||||||
|             await this.spacedUpdate.updateNowIfNecessary(); |             await this.spacedUpdate.updateNowIfNecessary(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return await this.parent.triggerCommand("runActiveNote", params); |         return await this.parent?.triggerCommand("runActiveNote", params); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async printActiveNoteEvent() { |     async printActiveNoteEvent() { | ||||||
|         if (!this.noteContext.isActive()) { |         if (!this.noteContext?.isActive()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -254,7 +283,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async exportAsPdfEvent() { |     async exportAsPdfEvent() { | ||||||
|         if (!this.noteContext.isActive()) { |         if (!this.noteContext?.isActive() || !this.note) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -266,18 +295,18 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     hoistedNoteChangedEvent({ ntxId }) { |     hoistedNoteChangedEvent({ ntxId }: EventData<"hoistedNoteChanged">) { | ||||||
|         if (this.isNoteContext(ntxId)) { |         if (this.isNoteContext(ntxId)) { | ||||||
|             this.refresh(); |             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
 |         // 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
 |         // 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.
 |         // 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
 |             // probably incorrect event
 | ||||||
|             // calling this.refresh() is not enough since the event needs to be propagated to children as well
 |             // 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
 |             // 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
 |             // this uses handleEvent to make sure that the ordinary content updates are propagated only in the subtree
 | ||||||
|             // to avoid the problem in #3365
 |             // to avoid the problem in #3365
 | ||||||
|             this.handleEvent("noteTypeMimeChanged", { noteId: this.noteId }); |             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 needs to have a triggerEvent so that e.g., note type (not in the component subtree) is updated
 | ||||||
|             this.triggerEvent("noteTypeMimeChanged", { noteId: this.noteId }); |             this.triggerEvent("noteTypeMimeChanged", { noteId: this.noteId }); | ||||||
|         } else { |         } else { | ||||||
| @@ -293,12 +322,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
| 
 | 
 | ||||||
|             const label = attrs.find( |             const label = attrs.find( | ||||||
|                 (attr) => |                 (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
 |                 // probably incorrect event
 | ||||||
|                 // calling this.refresh() is not enough since the event needs to be propagated to children as well
 |                 // calling this.refresh() is not enough since the event needs to be propagated to children as well
 | ||||||
|                 this.triggerEvent("noteTypeMimeChanged", { noteId: this.noteId }); |                 this.triggerEvent("noteTypeMimeChanged", { noteId: this.noteId }); | ||||||
| @@ -310,13 +339,13 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|         return this.spacedUpdate.isAllSavedAndTriggerUpdate(); |         return this.spacedUpdate.isAllSavedAndTriggerUpdate(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     readOnlyTemporarilyDisabledEvent({ noteContext }) { |     readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) { | ||||||
|         if (this.isNoteContext(noteContext.ntxId)) { |         if (this.isNoteContext(noteContext.ntxId)) { | ||||||
|             this.refresh(); |             this.refresh(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async executeInActiveNoteDetailWidgetEvent({ callback }) { |     async executeInActiveNoteDetailWidgetEvent({ callback }: EventData<"executeInActiveNoteDetailWidget">) { | ||||||
|         if (!this.isActiveNoteContext()) { |         if (!this.isActiveNoteContext()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -334,12 +363,15 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // without await as this otherwise causes deadlock through component mutex
 |         // 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, |                 isProtected: note.isProtected, | ||||||
|                 saveSelection: true, |                 saveSelection: true, | ||||||
|                 textEditor: await this.noteContext.getTextEditor() |                 textEditor: await this.noteContext.getTextEditor() | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     // used by cutToNote in CKEditor build
 |     // used by cutToNote in CKEditor build
 | ||||||
|     async saveNoteDetailNowCommand() { |     async saveNoteDetailNowCommand() { | ||||||
| @@ -347,12 +379,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     renderActiveNoteEvent() { |     renderActiveNoteEvent() { | ||||||
|         if (this.noteContext.isActive()) { |         if (this.noteContext?.isActive()) { | ||||||
|             this.refresh(); |             this.refresh(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async executeWithTypeWidgetEvent({ resolve, ntxId }) { |     async executeWithTypeWidgetEvent({ resolve, ntxId }: EventData<"executeWithTypeWidget">) { | ||||||
|         if (!this.isNoteContext(ntxId)) { |         if (!this.isNoteContext(ntxId)) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -368,7 +368,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                 const notePath = treeService.getNotePath(data.node); |                 const notePath = treeService.getNotePath(data.node); | ||||||
|  |  | ||||||
|                 const activeNoteContext = appContext.tabManager.getActiveContext(); |                 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), |             expand: (event, data) => this.setExpanded(data.node.data.branchId, true), | ||||||
|             collapse: (event, data) => this.setExpanded(data.node.data.branchId, false), |             collapse: (event, data) => this.setExpanded(data.node.data.branchId, false), | ||||||
| @@ -550,7 +554,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                     $span.append($refreshSearchButton); |                     $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( |                     const $createChildNoteButton = $(`<span class="tree-item-button add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on( | ||||||
|                         "click", |                         "click", | ||||||
|                         cancelClickPropagation |                         cancelClickPropagation | ||||||
|   | |||||||
| @@ -18,8 +18,9 @@ import attributeService from "../services/attributes.js"; | |||||||
| import RightPanelWidget from "./right_panel_widget.js"; | import RightPanelWidget from "./right_panel_widget.js"; | ||||||
| import options from "../services/options.js"; | import options from "../services/options.js"; | ||||||
| import OnClickButtonWidget from "./buttons/onclick_button.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 libraryLoader from "../services/library_loader.js"; | ||||||
|  | import type FNote from "../entities/fnote.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = `<div class="toc-widget">
 | const TPL = `<div class="toc-widget">
 | ||||||
|     <style> |     <style> | ||||||
| @@ -53,7 +54,16 @@ const TPL = `<div class="toc-widget"> | |||||||
|     <span class="toc"></span> |     <span class="toc"></span> | ||||||
| </div>`;
 | </div>`;
 | ||||||
| 
 | 
 | ||||||
|  | interface Toc { | ||||||
|  |     $toc: JQuery<HTMLElement>, | ||||||
|  |     headingCount: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export default class TocWidget extends RightPanelWidget { | export default class TocWidget extends RightPanelWidget { | ||||||
|  | 
 | ||||||
|  |     private $toc!: JQuery<HTMLElement>; | ||||||
|  |     private tocLabelValue?: string | null; | ||||||
|  | 
 | ||||||
|     get widgetTitle() { |     get widgetTitle() { | ||||||
|         return t("toc.table_of_contents"); |         return t("toc.table_of_contents"); | ||||||
|     } |     } | ||||||
| @@ -75,7 +85,17 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     isEnabled() { |     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() { |     async doRenderBody() { | ||||||
| @@ -83,36 +103,63 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|         this.$toc = this.$body.find(".toc"); |         this.$toc = this.$body.find(".toc"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async refreshWithNote(note) { |     async refreshWithNote(note: FNote) { | ||||||
|         /*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); |  | ||||||
| 
 | 
 | ||||||
|         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.toggleInt(false); | ||||||
|             this.triggerCommand("reEvaluateRightPaneVisibility"); |             this.triggerCommand("reEvaluateRightPaneVisibility"); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let $toc = "", |         if (!this.note || !this.noteContext?.viewScope) { | ||||||
|             headingCount = 0; |             return; | ||||||
|         // 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)); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.$toc.html($toc); |         // Check for type text unconditionally in case alwaysShowWidget is set
 | ||||||
|         if (["", "show"].includes(tocLabel?.value) || headingCount >= options.getInt("minTocHeadings")) { |         if (this.note.type === "text") { | ||||||
|             this.toggleInt(true); |             const blob = await note.getBlob(); | ||||||
|             this.noteContext.viewScope.tocPreviousVisible = true; |             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 { |                 } else { | ||||||
|             this.toggleInt(false); |                     console.warn("Unable to get content element for doctype"); | ||||||
|             this.noteContext.viewScope.tocPreviousVisible = false; |                 } | ||||||
|  |             }, 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"); |         this.triggerCommand("reEvaluateRightPaneVisibility"); | ||||||
| @@ -121,10 +168,10 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|     /** |     /** | ||||||
|      * Rendering formulas in strings using katex |      * Rendering formulas in strings using katex | ||||||
|      * |      * | ||||||
|      * @param {string} html Note's html content |      * @param html Note's html content | ||||||
|      * @returns {string} The HTML content with mathematical formulas rendered by KaTeX. |      * @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; |         const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g; | ||||||
|         var matches = [...html.matchAll(mathTextRegex)]; |         var matches = [...html.matchAll(mathTextRegex)]; | ||||||
|         let modifiedText = html; |         let modifiedText = html; | ||||||
| @@ -167,12 +214,12 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|     /** |     /** | ||||||
|      * Builds a jquery table of contents. |      * Builds a jquery table of contents. | ||||||
|      * |      * | ||||||
|      * @param {string} html Note's html content |      * @param html Note's html content | ||||||
|      * @returns {$toc: jQuery, headingCount: integer} ordered list table of headings, nested by heading level |      * @returns ordered list table of headings, nested by heading level | ||||||
|      *         with an onclick event that will cause the document to scroll to |      *         with an onclick event that will cause the document to scroll to | ||||||
|      *         the desired position. |      *         the desired position. | ||||||
|      */ |      */ | ||||||
|     async getToc(html) { |     async getToc(html: string): Promise<Toc> { | ||||||
|         // Regular expression for headings <h1>...</h1> using non-greedy
 |         // Regular expression for headings <h1>...</h1> using non-greedy
 | ||||||
|         // matching and backreferences
 |         // matching and backreferences
 | ||||||
|         const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi; |         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
 |         // Note heading 2 is the first level Trilium makes available to the note
 | ||||||
|         let curLevel = 2; |         let curLevel = 2; | ||||||
|         const $ols = [$toc]; |         const $ols = [$toc]; | ||||||
|         let headingCount; |         let headingCount = 0; | ||||||
|         for (let m = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) { |         for (let m = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) { | ||||||
|             //
 |             //
 | ||||||
|             // Nest/unnest whatever necessary number of ordered lists
 |             // Nest/unnest whatever necessary number of ordered lists
 | ||||||
|             //
 |             //
 | ||||||
|             const newLevel = m[1]; |             const newLevel = parseInt(m[1]); | ||||||
|             const levelDelta = newLevel - curLevel; |             const levelDelta = newLevel - curLevel; | ||||||
|             if (levelDelta > 0) { |             if (levelDelta > 0) { | ||||||
|                 // Open as many lists as newLevel - curLevel
 |                 // 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
 |      * 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) { |         while (true) { | ||||||
|             const $children = $toc.children(); |             const $children = $toc.children(); | ||||||
| 
 | 
 | ||||||
| @@ -248,16 +295,21 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|         return $toc; |         return $toc; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async jumpToHeading(headingIndex) { |     async jumpToHeading(headingIndex: number) { | ||||||
|  |         if (!this.note || !this.noteContext) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // A readonly note can change state to "readonly disabled
 |         // A readonly note can change state to "readonly disabled
 | ||||||
|         // temporarily" (ie "edit this note" button) without any
 |         // temporarily" (ie "edit this note" button) without any
 | ||||||
|         // intervening events, do the readonly calculation at navigation
 |         // intervening events, do the readonly calculation at navigation
 | ||||||
|         // time and not at outline creation time
 |         // time and not at outline creation time
 | ||||||
|         // See https://github.com/zadam/trilium/issues/2828
 |         // See https://github.com/zadam/trilium/issues/2828
 | ||||||
|  |         const isDocNote = this.note.type === "doc"; | ||||||
|         const isReadOnly = await this.noteContext.isReadOnly(); |         const isReadOnly = await this.noteContext.isReadOnly(); | ||||||
| 
 | 
 | ||||||
|         let $container; |         let $container; | ||||||
|         if (isReadOnly) { |         if (isReadOnly || isDocNote) { | ||||||
|             $container = await this.noteContext.getContentElement(); |             $container = await this.noteContext.getContentElement(); | ||||||
|         } else { |         } else { | ||||||
|             const textEditor = await this.noteContext.getTextEditor(); |             const textEditor = await this.noteContext.getTextEditor(); | ||||||
| @@ -269,26 +321,28 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async closeTocCommand() { |     async closeTocCommand() { | ||||||
|  |         if (this.noteContext?.viewScope) { | ||||||
|             this.noteContext.viewScope.tocTemporarilyHidden = true; |             this.noteContext.viewScope.tocTemporarilyHidden = true; | ||||||
|  |         } | ||||||
|         await this.refresh(); |         await this.refresh(); | ||||||
|         this.triggerCommand("reEvaluateRightPaneVisibility"); |         this.triggerCommand("reEvaluateRightPaneVisibility"); | ||||||
|         appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId }); |         appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async showTocWidgetEvent({ noteId }) { |     async showTocWidgetEvent({ noteId }: EventData<"showToc">) { | ||||||
|         if (this.noteId === noteId) { |         if (this.noteId === noteId) { | ||||||
|             await this.refresh(); |             await this.refresh(); | ||||||
|             this.triggerCommand("reEvaluateRightPaneVisibility"); |             this.triggerCommand("reEvaluateRightPaneVisibility"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async entitiesReloadedEvent({ loadResults }) { |     async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||||
|         if (loadResults.isNoteContentReloaded(this.noteId)) { |         if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) { | ||||||
|             await this.refresh(); |             await this.refresh(); | ||||||
|         } else if ( |         } else if ( | ||||||
|             loadResults |             loadResults | ||||||
|                 .getAttributeRows() |                 .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(); |             await this.refresh(); | ||||||
|         } |         } | ||||||
| @@ -35,6 +35,8 @@ import RibbonOptions from "./options/appearance/ribbon.js"; | |||||||
| import LocalizationOptions from "./options/appearance/i18n.js"; | import LocalizationOptions from "./options/appearance/i18n.js"; | ||||||
| import CodeBlockOptions from "./options/appearance/code_block.js"; | import CodeBlockOptions from "./options/appearance/code_block.js"; | ||||||
| import EditorOptions from "./options/text_notes/editor.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">
 | const TPL = `<div class="note-detail-content-widget note-detail-printable">
 | ||||||
|     <style> |     <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 class="note-detail-content-widget-content"></div> | ||||||
| </div>`;
 | </div>`;
 | ||||||
| 
 | 
 | ||||||
| const CONTENT_WIDGETS = { | const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = { | ||||||
|     _optionsAppearance: [LocalizationOptions, ThemeOptions, FontsOptions, CodeBlockOptions, ElectronIntegrationOptions, MaxContentWidthOptions, RibbonOptions], |     _optionsAppearance: [LocalizationOptions, ThemeOptions, FontsOptions, CodeBlockOptions, ElectronIntegrationOptions, MaxContentWidthOptions, RibbonOptions], | ||||||
|     _optionsShortcuts: [KeyboardShortcutsOptions], |     _optionsShortcuts: [KeyboardShortcutsOptions], | ||||||
|     _optionsTextNotes: [EditorOptions, HeadingStyleOptions, TableOfContentsOptions, HighlightsListOptions, TextAutoReadOnlySizeOptions], |     _optionsTextNotes: [EditorOptions, HeadingStyleOptions, TableOfContentsOptions, HighlightsListOptions, TextAutoReadOnlySizeOptions], | ||||||
| @@ -81,6 +83,9 @@ const CONTENT_WIDGETS = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default class ContentWidgetTypeWidget extends TypeWidget { | export default class ContentWidgetTypeWidget extends TypeWidget { | ||||||
|  | 
 | ||||||
|  |     private $content!: JQuery<HTMLElement>; | ||||||
|  | 
 | ||||||
|     static getType() { |     static getType() { | ||||||
|         return "contentWidget"; |         return "contentWidget"; | ||||||
|     } |     } | ||||||
| @@ -92,7 +97,7 @@ export default class ContentWidgetTypeWidget extends TypeWidget { | |||||||
|         super.doRender(); |         super.doRender(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async doRefresh(note) { |     async doRefresh(note: FNote) { | ||||||
|         this.$content.empty(); |         this.$content.empty(); | ||||||
|         this.children = []; |         this.children = []; | ||||||
| 
 | 
 | ||||||
| @@ -103,7 +108,9 @@ export default class ContentWidgetTypeWidget extends TypeWidget { | |||||||
|             for (const clazz of contentWidgets) { |             for (const clazz of contentWidgets) { | ||||||
|                 const widget = new clazz(); |                 const widget = new clazz(); | ||||||
| 
 | 
 | ||||||
|  |                 if (this.noteContext) { | ||||||
|                     await widget.handleEvent("setNoteContext", { noteContext: this.noteContext }); |                     await widget.handleEvent("setNoteContext", { noteContext: this.noteContext }); | ||||||
|  |                 } | ||||||
|                 this.child(widget); |                 this.child(widget); | ||||||
| 
 | 
 | ||||||
|                 this.$content.append(widget.render()); |                 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 type FNote from "../../entities/fnote.js"; | ||||||
|  | import { applySyntaxHighlight } from "../../services/syntax_highlight.js"; | ||||||
| import TypeWidget from "./type_widget.js"; | import TypeWidget from "./type_widget.js"; | ||||||
|  |  | ||||||
| const TPL = `<div class="note-detail-doc note-detail-printable"> | 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; |             padding: 15px; | ||||||
|             border-radius: 5px; |             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> |     </style> | ||||||
|  |  | ||||||
|     <div class="note-detail-doc-content"></div> |     <div class="note-detail-doc-content"></div> | ||||||
| @@ -34,19 +54,71 @@ export default class DocTypeWidget extends TypeWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async doRefresh(note: FNote) { |     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) { |             if (docName) { | ||||||
|                 // find doc based on language |                 // find doc based on language | ||||||
|             const lng = i18next.language; |                 const url = this.#getUrl(docName, i18next.language); | ||||||
|             this.$content.load(`${window.glob.appPath}/doc_notes/${lng}/${docName}.html`, (response, status) => { |                 this.$content.load(url, (response, status) => { | ||||||
|                     // fallback to english doc if no translation available |                     // fallback to english doc if no translation available | ||||||
|                     if (status === "error") { |                     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 { |             } else { | ||||||
|                 this.$content.empty(); |                 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 { | export default abstract class TypeWidget extends NoteContextAwareWidget { | ||||||
|  |  | ||||||
|     protected spacedUpdate!: SpacedUpdate; |     spacedUpdate!: SpacedUpdate; | ||||||
|  |  | ||||||
|     // for overriding |     // for overriding | ||||||
|     static getType() {} |     static getType() {} | ||||||
| @@ -45,6 +45,14 @@ export default abstract class TypeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|     focus() {} |     focus() {} | ||||||
|  |  | ||||||
|  |     scrollToEnd() { | ||||||
|  |         // Do nothing by default. | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     dataSaved() { | ||||||
|  |         // Do nothing by default. | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) { |     async readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) { | ||||||
|         if (this.isNoteContext(noteContext.ntxId)) { |         if (this.isNoteContext(noteContext.ntxId)) { | ||||||
|             await this.refresh(); |             await this.refresh(); | ||||||
|   | |||||||
| @@ -1606,3 +1606,9 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container { | |||||||
|     background: var(--hover-item-background-color); |     background: var(--hover-item-background-color); | ||||||
|     color: var(--hover-item-text-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_hidden_subtree": "Show Hidden Subtree", | ||||||
|     "show_help": "Show Help", |     "show_help": "Show Help", | ||||||
|     "about": "About TriliumNext Notes", |     "about": "About TriliumNext Notes", | ||||||
|     "logout": "Logout" |     "logout": "Logout", | ||||||
|  |     "show-cheatsheet": "Show Cheatsheet" | ||||||
|   }, |   }, | ||||||
|   "sync_status": { |   "sync_status": { | ||||||
|     "unknown": "<p>Sync status will be known once the next sync attempt starts.</p><p>Click to trigger sync now.</p>", |     "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": { |   "geo-map-context": { | ||||||
|     "open-location": "Open location", |     "open-location": "Open location", | ||||||
|     "remove-from-map": "Remove from map" |     "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 BBranch from "../../becca/entities/bbranch.js"; | ||||||
| import type { Response } from "express"; | import type { Response } from "express"; | ||||||
| import { RESOURCE_DIR } from "../resource_dir.js"; | 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) { | async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response | fs.WriteStream, setHeaders = true) { | ||||||
|     if (!["html", "markdown"].includes(format)) { |     if (!["html", "markdown"].includes(format)) { | ||||||
| @@ -485,8 +486,11 @@ ${markdownContent}`; | |||||||
|  |  | ||||||
|     const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {}; |     const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {}; | ||||||
|     const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames); |     const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames); | ||||||
|  |     if (!rootMeta) { | ||||||
|  |         throw new Error("Unable to create root meta."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const metaFile = { |     const metaFile: NoteMetaFile = { | ||||||
|         formatVersion: 2, |         formatVersion: 2, | ||||||
|         appVersion: packageInfo.version, |         appVersion: packageInfo.version, | ||||||
|         files: [rootMeta] |         files: [rootMeta] | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ import noteService from "./notes.js"; | |||||||
| import log from "./log.js"; | import log from "./log.js"; | ||||||
| import migrationService from "./migration.js"; | import migrationService from "./migration.js"; | ||||||
| import { t } from "i18next"; | import { t } from "i18next"; | ||||||
|  | import app_path from "./app_path.js"; | ||||||
|  | import { getHelpHiddenSubtreeData } from "./in_app_help.js"; | ||||||
|  |  | ||||||
| const LBTPL_ROOT = "_lbTplRoot"; | const LBTPL_ROOT = "_lbTplRoot"; | ||||||
| const LBTPL_BASE = "_lbTplBase"; | const LBTPL_BASE = "_lbTplBase"; | ||||||
| @@ -16,21 +18,21 @@ const LBTPL_BUILTIN_WIDGET = "_lbTplBuiltinWidget"; | |||||||
| const LBTPL_SPACER = "_lbTplSpacer"; | const LBTPL_SPACER = "_lbTplSpacer"; | ||||||
| const LBTPL_CUSTOM_WIDGET = "_lbTplCustomWidget"; | const LBTPL_CUSTOM_WIDGET = "_lbTplCustomWidget"; | ||||||
|  |  | ||||||
| interface Attribute { | interface HiddenSubtreeAttribute { | ||||||
|     type: AttributeType; |     type: AttributeType; | ||||||
|     name: string; |     name: string; | ||||||
|     isInheritable?: boolean; |     isInheritable?: boolean; | ||||||
|     value?: string; |     value?: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface Item { | export interface HiddenSubtreeItem { | ||||||
|     notePosition?: number; |     notePosition?: number; | ||||||
|     id: string; |     id: string; | ||||||
|     title: string; |     title: string; | ||||||
|     type: NoteType; |     type: NoteType; | ||||||
|     icon?: string; |     icon?: string; | ||||||
|     attributes?: Attribute[]; |     attributes?: HiddenSubtreeAttribute[]; | ||||||
|     children?: Item[]; |     children?: HiddenSubtreeItem[]; | ||||||
|     isExpanded?: boolean; |     isExpanded?: boolean; | ||||||
|     baseSize?: string; |     baseSize?: string; | ||||||
|     growthFactor?: string; |     growthFactor?: string; | ||||||
| @@ -54,9 +56,9 @@ enum Command { | |||||||
|  * duplicate subtrees. This way, all instances will generate the same structure with the same IDs. |  * 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 { |     return { | ||||||
|         id: "_hidden", |         id: "_hidden", | ||||||
|         title: t("hidden-subtree.root-title"), |         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: "_optionsOther", title: t("hidden-subtree.other"), type: "contentWidget", icon: "bx-dots-horizontal" }, | ||||||
|                     { id: "_optionsAdvanced", title: t("hidden-subtree.advanced-title"), type: "contentWidget" } |                     { 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); |     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) { |     if (!item.id || !item.type || !item.title) { | ||||||
|         throw new Error(`Item does not contain mandatory properties: ${JSON.stringify(item)}`); |         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) { |     for (const attr of attrs) { | ||||||
|         const attrId = note.noteId + "_" + attr.type.charAt(0) + attr.name; |         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({ |             new BAttribute({ | ||||||
|                 attributeId: attrId, |                 attributeId: attrId, | ||||||
|                 noteId: note.noteId, |                 noteId: note.noteId, | ||||||
| @@ -458,6 +470,10 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: Item, extraOp | |||||||
|                 value: attr.value, |                 value: attr.value, | ||||||
|                 isInheritable: false |                 isInheritable: false | ||||||
|             }).save(); |             }).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"), |             description: t("keyboard_actions.show-help"), | ||||||
|             scope: "window" |             scope: "window" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |             actionName: "showCheatsheet", | ||||||
|  |             defaultShortcuts: ["Shift+F1"], | ||||||
|  |             description: t("keyboard_actions.show-cheatsheet"), | ||||||
|  |             scope: "window" | ||||||
|  |         }, | ||||||
|  |  | ||||||
|         { |         { | ||||||
|             separator: t("keyboard_actions.text-note-operations") |             separator: t("keyboard_actions.text-note-operations") | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ const enum KeyboardActionNamesEnum { | |||||||
|     showRecentChanges, |     showRecentChanges, | ||||||
|     showSQLConsole, |     showSQLConsole, | ||||||
|     showBackendLog, |     showBackendLog, | ||||||
|  |     showCheatsheet, | ||||||
|     showHelp, |     showHelp, | ||||||
|     addLinkToText, |     addLinkToText, | ||||||
|     followLinkUnderCursor, |     followLinkUnderCursor, | ||||||
|   | |||||||
| @@ -1,6 +1,12 @@ | |||||||
| import type AttachmentMeta from "./attachment_meta.js"; | import type AttachmentMeta from "./attachment_meta.js"; | ||||||
| import type AttributeMeta from "./attribute_meta.js"; | import type AttributeMeta from "./attribute_meta.js"; | ||||||
|  |  | ||||||
|  | export interface NoteMetaFile { | ||||||
|  |     formatVersion: number; | ||||||
|  |     appVersion: string; | ||||||
|  |     files: NoteMeta[]; | ||||||
|  | } | ||||||
|  |  | ||||||
| export default interface NoteMeta { | export default interface NoteMeta { | ||||||
|     noteId?: string; |     noteId?: string; | ||||||
|     notePath?: string[]; |     notePath?: string[]; | ||||||
|   | |||||||
| @@ -146,7 +146,10 @@ function getAndValidateParent(params: GetValidateParams) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!params.ignoreForbiddenParents) { |     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.`); |             throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -88,10 +88,11 @@ | |||||||
|     "reset-zoom-level": "Reset zoom level", |     "reset-zoom-level": "Reset zoom level", | ||||||
|     "copy-without-formatting": "Copy selected text without formatting", |     "copy-without-formatting": "Copy selected text without formatting", | ||||||
|     "force-save-revision": "Force creating / saving new note revision of the active note", |     "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-book-properties": "Toggle Book Properties", | ||||||
|     "toggle-classic-editor-toolbar": "Toggle the Formatting tab for the editor with fixed toolbar", |     "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": { |   "login": { | ||||||
|     "title": "Login", |     "title": "Login", | ||||||
| @@ -241,7 +242,8 @@ | |||||||
|     "sync-title": "Sync", |     "sync-title": "Sync", | ||||||
|     "other": "Other", |     "other": "Other", | ||||||
|     "advanced-title": "Advanced", |     "advanced-title": "Advanced", | ||||||
|     "visible-launchers-title": "Visible Launchers" |     "visible-launchers-title": "Visible Launchers", | ||||||
|  |     "user-guide": "User Guide" | ||||||
|   }, |   }, | ||||||
|   "notes": { |   "notes": { | ||||||
|     "new-note": "New note", |     "new-note": "New note", | ||||||
|   | |||||||