mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Merge branch 'develop' into feature/MFA
This commit is contained in:
		
							
								
								
									
										16
									
								
								libraries/ckeditor/ckeditor-content.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								libraries/ckeditor/ckeditor-content.css
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ | |||||||
|     margin: 0 !important; |     margin: 0 !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition { | .admonition { | ||||||
|     --accent-color: var(--card-border-color); |     --accent-color: var(--card-border-color); | ||||||
|     border: 1px solid var(--accent-color); |     border: 1px solid var(--accent-color); | ||||||
|     box-shadow: var(--card-box-shadow); |     box-shadow: var(--card-box-shadow); | ||||||
| @@ -29,19 +29,19 @@ | |||||||
|     overflow: hidden; |     overflow: hidden; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition p:last-child { | .admonition p:last-child { | ||||||
|     margin-bottom: 0; |     margin-bottom: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition p, h2 { | .admonition p, h2 { | ||||||
|     margin-top: 0; |     margin-top: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition.note { --accent-color: #69c7ff; } | .admonition.note { --accent-color: #69c7ff; } | ||||||
| .ck-content .admonition.tip { --accent-color: #40c025; } | .admonition.tip { --accent-color: #40c025; } | ||||||
| .ck-content .admonition.important { --accent-color: #9839f7; } | .admonition.important { --accent-color: #9839f7; } | ||||||
| .ck-content .admonition.caution { --accent-color: #ff2e2e; } | .admonition.caution { --accent-color: #ff2e2e; } | ||||||
| .ck-content .admonition.warning { --accent-color: #e2aa03; } | .admonition.warning { --accent-color: #e2aa03; } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * CKEditor 5 (v41.0.0) content styles. |  * CKEditor 5 (v41.0.0) content styles. | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								libraries/ckeditor/ckeditor.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/ckeditor/ckeditor.js.map
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										16
									
								
								src/public/app/doc_notes/en/User Guide/style.css
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								src/public/app/doc_notes/en/User Guide/style.css
									
									
									
										generated
									
									
									
								
							| @@ -17,7 +17,7 @@ | |||||||
|     margin: 0 !important; |     margin: 0 !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition { | .admonition { | ||||||
|     --accent-color: var(--card-border-color); |     --accent-color: var(--card-border-color); | ||||||
|     border: 1px solid var(--accent-color); |     border: 1px solid var(--accent-color); | ||||||
|     box-shadow: var(--card-box-shadow); |     box-shadow: var(--card-box-shadow); | ||||||
| @@ -29,19 +29,19 @@ | |||||||
|     overflow: hidden; |     overflow: hidden; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition p:last-child { | .admonition p:last-child { | ||||||
|     margin-bottom: 0; |     margin-bottom: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition p, h2 { | .admonition p, h2 { | ||||||
|     margin-top: 0; |     margin-top: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition.note { --accent-color: #69c7ff; } | .admonition.note { --accent-color: #69c7ff; } | ||||||
| .ck-content .admonition.tip { --accent-color: #40c025; } | .admonition.tip { --accent-color: #40c025; } | ||||||
| .ck-content .admonition.important { --accent-color: #9839f7; } | .admonition.important { --accent-color: #9839f7; } | ||||||
| .ck-content .admonition.caution { --accent-color: #ff2e2e; } | .admonition.caution { --accent-color: #ff2e2e; } | ||||||
| .ck-content .admonition.warning { --accent-color: #e2aa03; } | .admonition.warning { --accent-color: #e2aa03; } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * CKEditor 5 (v41.0.0) content styles. |  * CKEditor 5 (v41.0.0) content styles. | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ import NoteMapRibbonWidget from "../widgets/ribbon_widgets/note_map.js"; | |||||||
| import NotePathsWidget from "../widgets/ribbon_widgets/note_paths.js"; | import NotePathsWidget from "../widgets/ribbon_widgets/note_paths.js"; | ||||||
| import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js"; | import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js"; | ||||||
| import RightPaneContainer from "../widgets/containers/right_pane_container.js"; | import RightPaneContainer from "../widgets/containers/right_pane_container.js"; | ||||||
| import EditButton from "../widgets/buttons/edit_button.js"; | import EditButton from "../widgets/floating_buttons/edit_button.js"; | ||||||
| import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js"; | import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js"; | ||||||
| import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js"; | import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js"; | ||||||
| import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js"; | import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js"; | ||||||
| @@ -89,6 +89,8 @@ import ContextualHelpButton from "../widgets/floating_buttons/help_button.js"; | |||||||
| import CloseZenButton from "../widgets/close_zen_button.js"; | import CloseZenButton from "../widgets/close_zen_button.js"; | ||||||
| import type { AppContext } from "./../components/app_context.js"; | import type { AppContext } from "./../components/app_context.js"; | ||||||
| import type { WidgetsByParent } from "../services/bundle.js"; | import type { WidgetsByParent } from "../services/bundle.js"; | ||||||
|  | import SwitchSplitOrientationButton from "../widgets/floating_buttons/switch_layout_button.js"; | ||||||
|  | import ToggleReadOnlyButton from "../widgets/floating_buttons/toggle_read_only_button.js"; | ||||||
|  |  | ||||||
| export default class DesktopLayout { | export default class DesktopLayout { | ||||||
|  |  | ||||||
| @@ -202,6 +204,8 @@ export default class DesktopLayout { | |||||||
|                                                         .child(new WatchedFileUpdateStatusWidget()) |                                                         .child(new WatchedFileUpdateStatusWidget()) | ||||||
|                                                         .child( |                                                         .child( | ||||||
|                                                             new FloatingButtons() |                                                             new FloatingButtons() | ||||||
|  |                                                                 .child(new SwitchSplitOrientationButton()) | ||||||
|  |                                                                 .child(new ToggleReadOnlyButton()) | ||||||
|                                                                 .child(new EditButton()) |                                                                 .child(new EditButton()) | ||||||
|                                                                 .child(new ShowTocWidgetButton()) |                                                                 .child(new ShowTocWidgetButton()) | ||||||
|                                                                 .child(new ShowHighlightsListWidgetButton()) |                                                                 .child(new ShowHighlightsListWidgetButton()) | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session | |||||||
| import ConfirmDialog from "../widgets/dialogs/confirm.js"; | import ConfirmDialog from "../widgets/dialogs/confirm.js"; | ||||||
| import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js"; | import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js"; | ||||||
| import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js"; | import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js"; | ||||||
| import EditButton from "../widgets/buttons/edit_button.js"; | import EditButton from "../widgets/floating_buttons/edit_button.js"; | ||||||
| import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js"; | import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js"; | ||||||
| import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js"; | import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js"; | ||||||
| import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; | import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; | ||||||
|   | |||||||
| @@ -23,6 +23,23 @@ async function removeAttributeById(noteId: string, attributeId: string) { | |||||||
|     await server.remove(`notes/${noteId}/attributes/${attributeId}`); |     await server.remove(`notes/${noteId}/attributes/${attributeId}`); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Removes a label identified by its name from the given note, if it exists. Note that the label must be owned, i.e. | ||||||
|  |  * it will not remove inherited attributes. | ||||||
|  |  * | ||||||
|  |  * @param note the note from which to remove the label. | ||||||
|  |  * @param labelName the name of the label to remove. | ||||||
|  |  * @returns `true` if an attribute was identified and removed, `false` otherwise. | ||||||
|  |  */ | ||||||
|  | function removeOwnedLabelByName(note: FNote, labelName: string) { | ||||||
|  |     const label = note.getOwnedLabel(labelName); | ||||||
|  |     if (label) { | ||||||
|  |         removeAttributeById(note.noteId, label.attributeId); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Sets the attribute of the given note to the provided value if its truthy, or removes the attribute if the value is falsy. |  * Sets the attribute of the given note to the provided value if its truthy, or removes the attribute if the value is falsy. | ||||||
|  * For an attribute with an empty value, pass an empty string instead. |  * For an attribute with an empty value, pass an empty string instead. | ||||||
| @@ -90,5 +107,6 @@ export default { | |||||||
|     setLabel, |     setLabel, | ||||||
|     setAttribute, |     setAttribute, | ||||||
|     removeAttributeById, |     removeAttributeById, | ||||||
|  |     removeOwnedLabelByName, | ||||||
|     isAffecting |     isAffecting | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,7 +1,26 @@ | |||||||
|  | import type { MermaidConfig } from "mermaid"; | ||||||
| import type { Mermaid } from "mermaid"; | import type { Mermaid } from "mermaid"; | ||||||
|  |  | ||||||
| let elkLoaded = false; | let elkLoaded = false; | ||||||
|  |  | ||||||
|  | export function getMermaidConfig(): MermaidConfig { | ||||||
|  |     const documentStyle = window.getComputedStyle(document.documentElement); | ||||||
|  |     const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme") as "default"; | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         theme: mermaidTheme.trim() as "default", | ||||||
|  |         securityLevel: "antiscript", | ||||||
|  |         flowchart: { useMaxWidth: false }, | ||||||
|  |         sequence: { useMaxWidth: false }, | ||||||
|  |         gantt: { useMaxWidth: false }, | ||||||
|  |         class: { useMaxWidth: false }, | ||||||
|  |         state: { useMaxWidth: false }, | ||||||
|  |         pie: { useMaxWidth: true }, | ||||||
|  |         journey: { useMaxWidth: false }, | ||||||
|  |         gitGraph: { useMaxWidth: false } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Determines whether the ELK extension of Mermaid.js needs to be loaded (which is a relatively large library), based on the |  * Determines whether the ELK extension of Mermaid.js needs to be loaded (which is a relatively large library), based on the | ||||||
|  * front-matter of the diagram and loads the library if needed. |  * front-matter of the diagram and loads the library if needed. | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -198,13 +198,13 @@ declare global { | |||||||
|         }; |         }; | ||||||
|         lineNumbers: boolean; |         lineNumbers: boolean; | ||||||
|         lineWrapping: boolean; |         lineWrapping: boolean; | ||||||
|         keyMap: "vim" | "default"; |         keyMap?: "vim" | "default"; | ||||||
|         lint: boolean; |         lint?: boolean; | ||||||
|         gutters: string[]; |         gutters?: string[]; | ||||||
|         tabindex: number; |         tabindex?: number; | ||||||
|         dragDrop: boolean; |         dragDrop?: boolean; | ||||||
|         placeholder: string; |         placeholder?: string; | ||||||
|         readOnly: boolean; |         readOnly?: boolean; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var CodeMirror: { |     var CodeMirror: { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import OnClickButtonWidget from "./onclick_button.js"; | import OnClickButtonWidget from "../buttons/onclick_button.js"; | ||||||
| import appContext from "../../components/app_context.js"; | import appContext from "../../components/app_context.js"; | ||||||
| import attributeService from "../../services/attributes.js"; | import attributeService from "../../services/attributes.js"; | ||||||
| import protectedSessionHolder from "../../services/protected_session_holder.js"; | import protectedSessionHolder from "../../services/protected_session_holder.js"; | ||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | import type { EventData } from "../../components/app_context.js"; | ||||||
|  | import { t } from "../../services/i18n.js"; | ||||||
|  | import options from "../../services/options.js"; | ||||||
|  | import NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||||
|  |  | ||||||
|  | const TPL = ` | ||||||
|  | <button type="button" | ||||||
|  |     class="switch-layout-button"> | ||||||
|  |     <span class="bx"></span> | ||||||
|  | </button> | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | export default class SwitchSplitOrientationButton extends NoteContextAwareWidget { | ||||||
|  |     isEnabled() { | ||||||
|  |         return super.isEnabled() | ||||||
|  |             && ["mermaid"].includes(this.note?.type ?? "") | ||||||
|  |             && this.note?.isContentAvailable() | ||||||
|  |             && !this.note?.hasLabel("readOnly") | ||||||
|  |             && this.noteContext?.viewScope?.viewMode === "default"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     doRender(): void { | ||||||
|  |         super.doRender(); | ||||||
|  |         this.$widget = $(TPL); | ||||||
|  |         this.$widget.on("click", () => { | ||||||
|  |             const currentOrientation = options.get("splitEditorOrientation"); | ||||||
|  |             options.save("splitEditorOrientation", toggleOrientation(currentOrientation)); | ||||||
|  |         }); | ||||||
|  |         this.#adjustIcon(); | ||||||
|  |         this.contentSized(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #adjustIcon() { | ||||||
|  |         const currentOrientation = options.get("splitEditorOrientation"); | ||||||
|  |         const upcomingOrientation = toggleOrientation(currentOrientation); | ||||||
|  |         const $icon = this.$widget.find("span.bx"); | ||||||
|  |         $icon | ||||||
|  |             .toggleClass("bxs-dock-bottom", upcomingOrientation === "vertical") | ||||||
|  |             .toggleClass("bxs-dock-left", upcomingOrientation === "horizontal"); | ||||||
|  |  | ||||||
|  |         if (upcomingOrientation === "vertical") { | ||||||
|  |             this.$widget.attr("title", t("switch_layout_button.title_vertical")); | ||||||
|  |         } else { | ||||||
|  |             this.$widget.attr("title", t("switch_layout_button.title_horizontal")); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||||
|  |         if (loadResults.isOptionReloaded("splitEditorOrientation")) { | ||||||
|  |             this.#adjustIcon(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function toggleOrientation(orientation: string) { | ||||||
|  |     if (orientation === "horizontal") { | ||||||
|  |         return "vertical"; | ||||||
|  |     } else { | ||||||
|  |         return "horizontal"; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,48 @@ | |||||||
|  | import type FNote from "../../entities/fnote.js"; | ||||||
|  | import attributes from "../../services/attributes.js"; | ||||||
|  | import { t } from "../../services/i18n.js"; | ||||||
|  | import OnClickButtonWidget from "../buttons/onclick_button.js"; | ||||||
|  |  | ||||||
|  | export default class ToggleReadOnlyButton extends OnClickButtonWidget { | ||||||
|  |  | ||||||
|  |     private isReadOnly?: boolean; | ||||||
|  |  | ||||||
|  |     constructor() { | ||||||
|  |         super(); | ||||||
|  |  | ||||||
|  |         this | ||||||
|  |             .title(() => this.isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")) | ||||||
|  |             .titlePlacement("bottom") | ||||||
|  |             .icon(() => this.isReadOnly ? "bx-lock-open-alt" : "bx-lock-alt") | ||||||
|  |             .onClick(() => this.#toggleReadOnly()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #toggleReadOnly() { | ||||||
|  |         if (!this.noteId || !this.note) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (this.isReadOnly) { | ||||||
|  |             attributes.removeOwnedLabelByName(this.note, "readOnly"); | ||||||
|  |         } else { | ||||||
|  |             attributes.setLabel(this.noteId, "readOnly"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async refreshWithNote(note: FNote | null | undefined) { | ||||||
|  |         const isReadOnly = !!note?.hasLabel("readOnly"); | ||||||
|  |  | ||||||
|  |         if (isReadOnly !== this.isReadOnly) { | ||||||
|  |             this.isReadOnly = isReadOnly; | ||||||
|  |             this.refreshIcon(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     isEnabled() { | ||||||
|  |         return super.isEnabled() | ||||||
|  |             && this.note?.type === "mermaid" | ||||||
|  |             && this.note?.isContentAvailable() | ||||||
|  |             && this.noteContext?.viewScope?.viewMode === "default"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -19,25 +19,10 @@ const TPL = `<div class="note-map-widget"> | |||||||
|             overflow: hidden; |             overflow: hidden; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .map-type-switcher { |  | ||||||
|             position: absolute; |  | ||||||
|             top: 10px; |  | ||||||
|             left: 10px; |  | ||||||
|             z-index: 10; /* should be below dropdown (note actions) */ |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         .map-type-switcher button.bx { |  | ||||||
|             font-size: 130%; |  | ||||||
|             padding: 1px 10px 1px 10px; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /* Style Ui Element to Drag Nodes */ |         /* Style Ui Element to Drag Nodes */ | ||||||
|         .fixnodes-type-switcher { |         .fixnodes-type-switcher { | ||||||
|             position: absolute; |  | ||||||
|             display: flex; |             display: flex; | ||||||
|             align-items: center; |             align-items: center; | ||||||
|             bottom: 10px; |  | ||||||
|             left: 10px; |  | ||||||
|             z-index: 10; /* should be below dropdown (note actions) */ |             z-index: 10; /* should be below dropdown (note actions) */ | ||||||
|             border-radius: .2rem; |             border-radius: .2rem; | ||||||
|         } |         } | ||||||
| @@ -94,14 +79,14 @@ const TPL = `<div class="note-map-widget"> | |||||||
|  |  | ||||||
|     </style> |     </style> | ||||||
|  |  | ||||||
|     <div class="btn-group btn-group-sm map-type-switcher" role="group"> |     <div class="btn-group btn-group-sm map-type-switcher content-floating-buttons top-left" role="group"> | ||||||
|       <button type="button" class="btn bx bx-network-chart tn-tool-button" title="${t("note-map.button-link-map")}" data-type="link"></button> |       <button type="button" class="btn bx bx-network-chart tn-tool-button" title="${t("note-map.button-link-map")}" data-type="link"></button> | ||||||
|       <button type="button" class="btn bx bx-sitemap tn-tool-button" title="${t("note-map.button-tree-map")}" data-type="tree"></button> |       <button type="button" class="btn bx bx-sitemap tn-tool-button" title="${t("note-map.button-tree-map")}" data-type="tree"></button> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <! UI for dragging Notes and link force > |     <! UI for dragging Notes and link force > | ||||||
|  |  | ||||||
|     <div class=" btn-group-sm fixnodes-type-switcher" role="group"> |     <div class="btn-group-sm fixnodes-type-switcher content-floating-buttons bottom-left" role="group"> | ||||||
|       <button type="button" data-toggle="button" class="btn bx bx-lock-alt tn-tool-button" title="${t("note_map.fix-nodes")}" data-type="moveable"></button> |       <button type="button" data-toggle="button" class="btn bx bx-lock-alt tn-tool-button" title="${t("note_map.fix-nodes")}" data-type="moveable"></button> | ||||||
|       <input type="range" class="slider" min="1" title="${t("note_map.link-distance")}" max="100" value="40" > |       <input type="range" class="slider" min="1" title="${t("note_map.link-distance")}" max="100" value="40" > | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -123,7 +123,7 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.$widget.find(".editability-select-container").toggle(this.note && ["text", "code"].includes(this.note.type)); |         this.$widget.find(".editability-select-container").toggle(this.note && ["text", "code", "mermaid"].includes(this.note.type)); | ||||||
|         this.$widget.find(".note-language-container").toggle(this.note && ["text"].includes(this.note.type)); |         this.$widget.find(".note-language-container").toggle(this.note && ["text"].includes(this.note.type)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,15 +4,20 @@ import EditableCodeTypeWidget from "./editable_code.js"; | |||||||
| import TypeWidget from "./type_widget.js"; | import TypeWidget from "./type_widget.js"; | ||||||
| import Split from "split.js"; | import Split from "split.js"; | ||||||
| import { DEFAULT_GUTTER_SIZE } from "../../services/resizer.js"; | import { DEFAULT_GUTTER_SIZE } from "../../services/resizer.js"; | ||||||
|  | import options from "../../services/options.js"; | ||||||
|  | import type SwitchSplitOrientationButton from "../floating_buttons/switch_layout_button.js"; | ||||||
|  | import type { EventData } from "../../components/app_context.js"; | ||||||
|  | import type OnClickButtonWidget from "../buttons/onclick_button.js"; | ||||||
|  |  | ||||||
| const TPL = `\ | const TPL = `\ | ||||||
| <div class="note-detail-split note-detail-printable split-horizontal"> | <div class="note-detail-split note-detail-printable"> | ||||||
|     <div class="note-detail-split-first-col"> |     <div class="note-detail-split-editor-col"> | ||||||
|         <div class="note-detail-split-editor"></div> |         <div class="note-detail-split-editor"></div> | ||||||
|         <div class="note-detail-error-container alert alert-warning hidden-ext"></div> |         <div class="admonition caution note-detail-error-container hidden-ext"></div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="note-detail-split-second-col"> |     <div class="note-detail-split-preview-col"> | ||||||
|         <div class="note-detail-split-preview"></div> |         <div class="note-detail-split-preview"></div> | ||||||
|  |         <div class="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group"></div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <style> |     <style> | ||||||
| @@ -21,8 +26,13 @@ const TPL = `\ | |||||||
|             height: 100%; |             height: 100%; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .note-detail-split-first-col { |         .note-detail-split-editor-col { | ||||||
|             display: flex; |             display: flex; | ||||||
|  |             flex-direction: column; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .note-detail-split-preview-col { | ||||||
|  |             position: relative; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .note-detail-split .note-detail-split-editor { |         .note-detail-split .note-detail-split-editor { | ||||||
| @@ -32,11 +42,14 @@ const TPL = `\ | |||||||
|  |  | ||||||
|         .note-detail-split .note-detail-error-container { |         .note-detail-split .note-detail-error-container { | ||||||
|             font-family: var(--monospace-font-family); |             font-family: var(--monospace-font-family); | ||||||
|             margin: 0.1em; |             margin: 5px; | ||||||
|  |             white-space: pre-wrap; | ||||||
|  |             font-size: 0.85em; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .note-detail-split .note-detail-split-preview { |         .note-detail-split .note-detail-split-preview { | ||||||
|             transition: opacity 250ms ease-in-out; |             transition: opacity 250ms ease-in-out; | ||||||
|  |             height: 100%; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .note-detail-split .note-detail-split-preview.on-error { |         .note-detail-split .note-detail-split-preview.on-error { | ||||||
| @@ -45,11 +58,12 @@ const TPL = `\ | |||||||
|  |  | ||||||
|         /* Horizontal layout */ |         /* Horizontal layout */ | ||||||
|  |  | ||||||
|         .note-detail-split.split-horizontal > .note-detail-split-second-col { |         .note-detail-split.split-horizontal > .note-detail-split-preview-col { | ||||||
|             border-left: 1px solid var(--main-border-color); |             border-left: 1px solid var(--main-border-color); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .note-detail-split.split-horizontal > div { |         .note-detail-split.split-horizontal > .note-detail-split-editor-col, | ||||||
|  |         .note-detail-split.split-horizontal > .note-detail-split-preview-col { | ||||||
|             height: 100%; |             height: 100%; | ||||||
|             width: 50%; |             width: 50%; | ||||||
|         } |         } | ||||||
| @@ -58,13 +72,31 @@ const TPL = `\ | |||||||
|             height: 100%; |             height: 100%; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .note-detail-split-first-col { |         /* Vertical layout */ | ||||||
|  |  | ||||||
|  |         .note-detail-split.split-vertical { | ||||||
|             flex-direction: column; |             flex-direction: column; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /* Vertical layout */ |         .note-detail-split.split-vertical > .note-detail-split-editor-col, | ||||||
|  |         .note-detail-split.split-vertical > .note-detail-split-preview-col { | ||||||
|  |             width: 100%; | ||||||
|  |             height: 50%; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .note-detail-split.split-vertical > .note-detail-split-editor-col { | ||||||
|  |             border-top: 1px solid var(--main-border-color); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .note-detail-split.split-vertical .note-detail-split-preview-col { | ||||||
|  |             order: -1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* Read-only view */ | ||||||
|  |  | ||||||
|  |         .note-detail-split.split-read-only .note-detail-split-preview-col { | ||||||
|  |             width: 100%; | ||||||
|  |         } | ||||||
|     </style> |     </style> | ||||||
| </div> | </div> | ||||||
| `; | `; | ||||||
| @@ -76,17 +108,20 @@ const TPL = `\ | |||||||
|  * |  * | ||||||
|  * - The two panes are resizeable via a split, on desktop. The split can be optionally customized via {@link buildSplitExtraOptions}. |  * - The two panes are resizeable via a split, on desktop. The split can be optionally customized via {@link buildSplitExtraOptions}. | ||||||
|  * - Can display errors to the user via {@link setError}. |  * - Can display errors to the user via {@link setError}. | ||||||
|  |  * - Horizontal or vertical orientation for the editor/preview split, adjustable via {@link SwitchSplitOrientationButton}. | ||||||
|  */ |  */ | ||||||
| export default abstract class AbstractSplitTypeWidget extends TypeWidget { | export default abstract class AbstractSplitTypeWidget extends TypeWidget { | ||||||
|  |  | ||||||
|     private splitInstance?: Split.Instance; |     private splitInstance?: Split.Instance; | ||||||
|  |  | ||||||
|     protected $preview!: JQuery<HTMLElement>; |     protected $preview!: JQuery<HTMLElement>; | ||||||
|     private $firstCol!: JQuery<HTMLElement>; |     private $editorCol!: JQuery<HTMLElement>; | ||||||
|     private $secondCol!: JQuery<HTMLElement>; |     private $previewCol!: JQuery<HTMLElement>; | ||||||
|     private $editor!: JQuery<HTMLElement>; |     private $editor!: JQuery<HTMLElement>; | ||||||
|     private $errorContainer!: JQuery<HTMLElement>; |     private $errorContainer!: JQuery<HTMLElement>; | ||||||
|     private editorTypeWidget: EditableCodeTypeWidget; |     private editorTypeWidget: EditableCodeTypeWidget; | ||||||
|  |     private layoutOrientation?: "horizontal" | "vertical"; | ||||||
|  |     private isReadOnly?: boolean; | ||||||
|  |  | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
| @@ -98,13 +133,29 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { | |||||||
|     doRender(): void { |     doRender(): void { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|  |  | ||||||
|         this.$firstCol = this.$widget.find(".note-detail-split-first-col"); |         // Preview pane | ||||||
|         this.$secondCol = this.$widget.find(".note-detail-split-second-col"); |         this.$previewCol = this.$widget.find(".note-detail-split-preview-col"); | ||||||
|         this.$preview = this.$widget.find(".note-detail-split-preview"); |         this.$preview = this.$widget.find(".note-detail-split-preview"); | ||||||
|  |  | ||||||
|  |         // Editor pane | ||||||
|  |         this.$editorCol = this.$widget.find(".note-detail-split-editor-col"); | ||||||
|         this.$editor = this.$widget.find(".note-detail-split-editor"); |         this.$editor = this.$widget.find(".note-detail-split-editor"); | ||||||
|         this.$editor.append(this.editorTypeWidget.render()); |         this.$editor.append(this.editorTypeWidget.render()); | ||||||
|         this.$errorContainer = this.$widget.find(".note-detail-error-container"); |         this.$errorContainer = this.$widget.find(".note-detail-error-container"); | ||||||
|         this.#setupResizer(); |         this.#adjustLayoutOrientation(); | ||||||
|  |  | ||||||
|  |         // Preview pane buttons | ||||||
|  |         const $previewButtons = this.$previewCol.find(".preview-buttons"); | ||||||
|  |         const previewButtons = this.buildPreviewButtons(); | ||||||
|  |         $previewButtons.toggle(previewButtons.length > 0); | ||||||
|  |         for (const previewButton of previewButtons) { | ||||||
|  |             const $button = previewButton.render(); | ||||||
|  |             $button.removeClass("button-widget") | ||||||
|  |                 .addClass("btn") | ||||||
|  |                 .addClass("tn-tool-button"); | ||||||
|  |             $previewButtons.append($button); | ||||||
|  |             previewButton.refreshIcon(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         super.doRender(); |         super.doRender(); | ||||||
|     } |     } | ||||||
| @@ -115,27 +166,60 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async doRefresh(note: FNote | null | undefined) { |     async doRefresh(note: FNote | null | undefined) { | ||||||
|         await this.editorTypeWidget.initialized; |         this.#adjustLayoutOrientation(); | ||||||
|  |  | ||||||
|         if (note) { |         if (note && !this.isReadOnly) { | ||||||
|  |             await this.editorTypeWidget.initialized; | ||||||
|             this.editorTypeWidget.noteContext = this.noteContext; |             this.editorTypeWidget.noteContext = this.noteContext; | ||||||
|             this.editorTypeWidget.spacedUpdate = this.spacedUpdate; |             this.editorTypeWidget.spacedUpdate = this.spacedUpdate; | ||||||
|             this.editorTypeWidget.doRefresh(note); |             this.editorTypeWidget.doRefresh(note); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #adjustLayoutOrientation() { | ||||||
|  |         // Read-only | ||||||
|  |         const isReadOnly = this.note?.hasLabel("readOnly"); | ||||||
|  |         if (this.isReadOnly !== isReadOnly) { | ||||||
|  |             this.$editorCol.toggle(!isReadOnly); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Vertical vs horizontal layout | ||||||
|  |         const layoutOrientation = options.get("splitEditorOrientation") ?? "horizontal"; | ||||||
|  |         if (this.layoutOrientation === layoutOrientation && this.isReadOnly === isReadOnly) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.$widget | ||||||
|  |             .toggleClass("split-horizontal", !isReadOnly && layoutOrientation === "horizontal") | ||||||
|  |             .toggleClass("split-vertical", !isReadOnly && layoutOrientation === "vertical") | ||||||
|  |             .toggleClass("split-read-only", isReadOnly); | ||||||
|  |         this.layoutOrientation = layoutOrientation as ("horizontal" | "vertical"); | ||||||
|  |         this.isReadOnly = isReadOnly; | ||||||
|  |         this.#setupResizer(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #setupResizer() { |     #setupResizer() { | ||||||
|         if (!utils.isDesktop()) { |         if (!utils.isDesktop()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         let elements = [ this.$editorCol[0], this.$previewCol[0] ]; | ||||||
|  |         if (this.layoutOrientation === "vertical") { | ||||||
|  |             elements.reverse(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         this.splitInstance?.destroy(); |         this.splitInstance?.destroy(); | ||||||
|         this.splitInstance = Split([ this.$firstCol[0], this.$secondCol[0] ], { |  | ||||||
|  |         if (!this.isReadOnly) { | ||||||
|  |             this.splitInstance = Split(elements, { | ||||||
|                 sizes: [ 50, 50 ], |                 sizes: [ 50, 50 ], | ||||||
|             direction: "horizontal", |                 direction: this.layoutOrientation, | ||||||
|                 gutterSize: DEFAULT_GUTTER_SIZE, |                 gutterSize: DEFAULT_GUTTER_SIZE, | ||||||
|                 ...this.buildSplitExtraOptions() |                 ...this.buildSplitExtraOptions() | ||||||
|             }); |             }); | ||||||
|  |         } else { | ||||||
|  |             this.splitInstance = undefined; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -154,6 +238,10 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildPreviewButtons(): OnClickButtonWidget[] { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     setError(message: string | null | undefined) { |     setError(message: string | null | undefined) { | ||||||
|         this.$errorContainer.toggleClass("hidden-ext", !message); |         this.$errorContainer.toggleClass("hidden-ext", !message); | ||||||
|         this.$preview.toggleClass("on-error", !!message); |         this.$preview.toggleClass("on-error", !!message); | ||||||
| @@ -163,4 +251,11 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { | |||||||
|     getData() { |     getData() { | ||||||
|         return this.editorTypeWidget.getData(); |         return this.editorTypeWidget.getData(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||||
|  |         if (loadResults.isOptionReloaded("splitEditorOrientation")) { | ||||||
|  |             this.refresh(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| import type { EventData } from "../../components/app_context.js"; | import type { EventData } from "../../components/app_context.js"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
|  | import { t } from "../../services/i18n.js"; | ||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
|  | import OnClickButtonWidget from "../buttons/onclick_button.js"; | ||||||
| import AbstractSplitTypeWidget from "./abstract_split_type_widget.js"; | import AbstractSplitTypeWidget from "./abstract_split_type_widget.js"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -48,11 +50,20 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy | |||||||
|         const blob = await note?.getBlob(); |         const blob = await note?.getBlob(); | ||||||
|         const content = blob?.content || ""; |         const content = blob?.content || ""; | ||||||
|         this.onContentChanged(content, true); |         this.onContentChanged(content, true); | ||||||
|  |  | ||||||
|  |         // Save the SVG when entering a note only when it does not have an attachment. | ||||||
|  |         this.note?.getAttachments().then((attachments) => { | ||||||
|  |             const attachmentName = `${this.attachmentName}.svg`; | ||||||
|  |             if (!attachments.find((a) => a.title === attachmentName)) { | ||||||
|  |                 this.#saveSvg(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getData(): { content: string; } { |     getData(): { content: string; } { | ||||||
|         const data = super.getData(); |         const data = super.getData(); | ||||||
|         this.onContentChanged(data.content, false); |         this.onContentChanged(data.content, false); | ||||||
|  |         this.#saveSvg(); | ||||||
|         return data; |         return data; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -70,11 +81,6 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy | |||||||
|         let svg: string = ""; |         let svg: string = ""; | ||||||
|         try { |         try { | ||||||
|             svg = await this.renderSvg(content); |             svg = await this.renderSvg(content); | ||||||
|         } catch (e: unknown) { |  | ||||||
|             // Rendering failed. |  | ||||||
|             this.setError((e as Error)?.message); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|             // Rendering was succesful. |             // Rendering was succesful. | ||||||
|             this.setError(null); |             this.setError(null); | ||||||
| @@ -84,10 +90,13 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             this.svg = svg; |             this.svg = svg; | ||||||
|  |  | ||||||
|             this.$renderContainer.html(svg); |             this.$renderContainer.html(svg); | ||||||
|  |         } catch (e: unknown) { | ||||||
|  |             // Rendering failed. | ||||||
|  |             this.setError((e as Error)?.message); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         await this.#setupPanZoom(!recenter); |         await this.#setupPanZoom(!recenter); | ||||||
|         this.#saveSvg(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #saveSvg() { |     #saveSvg() { | ||||||
| @@ -150,7 +159,7 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy | |||||||
|         const svgPanZoom = (await import("svg-pan-zoom")).default; |         const svgPanZoom = (await import("svg-pan-zoom")).default; | ||||||
|         const zoomInstance = svgPanZoom($svgEl[0], { |         const zoomInstance = svgPanZoom($svgEl[0], { | ||||||
|             zoomEnabled: true, |             zoomEnabled: true, | ||||||
|             controlIconsEnabled: true |             controlIconsEnabled: false | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         if (preservePanZoom && pan && zoom) { |         if (preservePanZoom && pan && zoom) { | ||||||
| @@ -159,6 +168,7 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy | |||||||
|             zoomInstance.pan(pan); |             zoomInstance.pan(pan); | ||||||
|         } else { |         } else { | ||||||
|             // New instance, reposition properly. |             // New instance, reposition properly. | ||||||
|  |             zoomInstance.resize(); | ||||||
|             zoomInstance.center(); |             zoomInstance.center(); | ||||||
|             zoomInstance.fit(); |             zoomInstance.fit(); | ||||||
|         } |         } | ||||||
| @@ -172,6 +182,26 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     buildPreviewButtons(): OnClickButtonWidget[] { | ||||||
|  |         return [ | ||||||
|  |             new OnClickButtonWidget() | ||||||
|  |                 .icon("bx-zoom-in") | ||||||
|  |                 .title(t("relation_map_buttons.zoom_in_title")) | ||||||
|  |                 .titlePlacement("top") | ||||||
|  |                 .onClick(() => this.zoomInstance?.zoomIn()) | ||||||
|  |             , new OnClickButtonWidget() | ||||||
|  |                 .icon("bx-zoom-out") | ||||||
|  |                 .title(t("relation_map_buttons.zoom_out_title")) | ||||||
|  |                 .titlePlacement("top") | ||||||
|  |                 .onClick(() => this.zoomInstance?.zoomOut()) | ||||||
|  |             , new OnClickButtonWidget() | ||||||
|  |                 .icon("bx-crop") | ||||||
|  |                 .title(t("relation_map_buttons.reset_pan_zoom_title")) | ||||||
|  |                 .titlePlacement("top") | ||||||
|  |                 .onClick(() => this.zoomHandler()) | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #cleanUpZoom() { |     #cleanUpZoom() { | ||||||
|         if (this.zoomInstance) { |         if (this.zoomInstance) { | ||||||
|             this.zoomInstance.destroy(); |             this.zoomInstance.destroy(); | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ import toast from "../../services/toast.js"; | |||||||
| import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js"; | import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js"; | ||||||
| import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js"; | import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import { getMermaidConfig } from "./mermaid.js"; | import { getMermaidConfig } from "../../services/mermaid.js"; | ||||||
|  |  | ||||||
| const ENABLE_INSPECTOR = false; | const ENABLE_INSPECTOR = false; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import type { MermaidConfig } from "mermaid"; | import { getMermaidConfig, loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js"; | ||||||
| import { loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js"; |  | ||||||
| import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js"; | import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js"; | ||||||
|  |  | ||||||
| let idCounter = 1; | let idCounter = 1; | ||||||
| @@ -34,22 +33,3 @@ export class MermaidTypeWidget extends AbstractSvgSplitTypeWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| export function getMermaidConfig(): MermaidConfig { |  | ||||||
|     const documentStyle = window.getComputedStyle(document.documentElement); |  | ||||||
|     const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme") as "default"; |  | ||||||
|  |  | ||||||
|     return { |  | ||||||
|         theme: mermaidTheme.trim() as "default", |  | ||||||
|         securityLevel: "antiscript", |  | ||||||
|         flowchart: { useMaxWidth: false }, |  | ||||||
|         sequence: { useMaxWidth: false }, |  | ||||||
|         gantt: { useMaxWidth: false }, |  | ||||||
|         class: { useMaxWidth: false }, |  | ||||||
|         state: { useMaxWidth: false }, |  | ||||||
|         pie: { useMaxWidth: true }, |  | ||||||
|         journey: { useMaxWidth: false }, |  | ||||||
|         gitGraph: { useMaxWidth: false } |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { applySyntaxHighlight } from "../../services/syntax_highlight.js"; | |||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import type { EventData } from "../../components/app_context.js"; | import type { EventData } from "../../components/app_context.js"; | ||||||
| import { getLocaleById } from "../../services/i18n.js"; | import { getLocaleById } from "../../services/i18n.js"; | ||||||
| import { getMermaidConfig } from "./mermaid.js"; | import { getMermaidConfig } from "../../services/mermaid.js"; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="note-detail-readonly-text note-detail-printable"> | <div class="note-detail-readonly-text note-detail-printable"> | ||||||
| @@ -142,7 +142,10 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|  |  | ||||||
|         // Initialize mermaid |         // Initialize mermaid | ||||||
|         const mermaid = (await import("mermaid")).default; |         const mermaid = (await import("mermaid")).default; | ||||||
|         mermaid.init(getMermaidConfig(), this.$content.find(".mermaid-diagram")[0]); |         mermaid.initialize(getMermaidConfig()); | ||||||
|  |         mermaid.run({ | ||||||
|  |             nodes: this.$content.find(".mermaid-diagram") | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async refreshIncludedNoteEvent({ noteId }: EventData<"refreshIncludedNote">) { |     async refreshIncludedNoteEvent({ noteId }: EventData<"refreshIncludedNote">) { | ||||||
|   | |||||||
| @@ -1722,7 +1722,7 @@ footer.file-footer button { | |||||||
|     margin: 5px; |     margin: 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition { | .admonition { | ||||||
|     --accent-color: var(--card-border-color); |     --accent-color: var(--card-border-color); | ||||||
|     border: 1px solid var(--accent-color); |     border: 1px solid var(--accent-color); | ||||||
|     box-shadow: var(--card-box-shadow); |     box-shadow: var(--card-box-shadow); | ||||||
| @@ -1735,11 +1735,11 @@ footer.file-footer button { | |||||||
|     overflow: hidden; |     overflow: hidden; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition p:last-child { | .admonition p:last-child { | ||||||
|     margin-bottom: 0; |     margin-bottom: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition::before {   | .admonition::before {   | ||||||
|     color: var(--accent-color); |     color: var(--accent-color); | ||||||
|     font-family: boxicons !important; |     font-family: boxicons !important; | ||||||
|     position: absolute; |     position: absolute; | ||||||
| @@ -1747,14 +1747,43 @@ footer.file-footer button { | |||||||
|     left: 1em; |     left: 1em; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ck-content .admonition.note { --accent-color: #69c7ff; } | .admonition.note { --accent-color: #69c7ff; } | ||||||
| .ck-content .admonition.tip { --accent-color: #40c025; } | .admonition.tip { --accent-color: #40c025; } | ||||||
| .ck-content .admonition.important { --accent-color: #9839f7; } | .admonition.important { --accent-color: #9839f7; } | ||||||
| .ck-content .admonition.caution { --accent-color: #ff2e2e; } | .admonition.caution { --accent-color: #ff2e2e; } | ||||||
| .ck-content .admonition.warning { --accent-color: #e2aa03; } | .admonition.warning { --accent-color: #e2aa03; } | ||||||
|  |  | ||||||
| .ck-content .admonition.note::before { content: "\eb21"; } | .admonition.note::before { content: "\eb21"; } | ||||||
| .ck-content .admonition.tip::before { content: "\ea0d"; } | .admonition.tip::before { content: "\ea0d"; } | ||||||
| .ck-content .admonition.important::before { content: "\ea7c"; } | .admonition.important::before { content: "\ea7c"; } | ||||||
| .ck-content .admonition.caution::before { content: "\eac7"; } | .admonition.caution::before { content: "\eac7"; } | ||||||
| .ck-content .admonition.warning::before { content: "\eac5"; } | .admonition.warning::before { content: "\eac5"; } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * In-content floating buttons | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | .content-floating-buttons { | ||||||
|  |     position: absolute; | ||||||
|  |     z-index: 10; /* should be below dropdown (note actions) */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-floating-buttons.top-left { | ||||||
|  |     top: 10px; | ||||||
|  |     left: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-floating-buttons.bottom-left { | ||||||
|  |     bottom: 10px; | ||||||
|  |     left: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-floating-buttons.bottom-right { | ||||||
|  |     bottom: 10px; | ||||||
|  |     right: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-floating-buttons button.bx { | ||||||
|  |     font-size: 130%; | ||||||
|  |     padding: 1px 10px 1px 10px; | ||||||
|  | } | ||||||
| @@ -1455,9 +1455,6 @@ | |||||||
|     "title": "高亮列表", |     "title": "高亮列表", | ||||||
|     "options": "选项" |     "options": "选项" | ||||||
|   }, |   }, | ||||||
|   "mermaid": { |  | ||||||
|     "diagram_error": "图表无法显示。 请参考 <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">帮助文档和示例</a>。" |  | ||||||
|   }, |  | ||||||
|   "quick-search": { |   "quick-search": { | ||||||
|     "placeholder": "快速搜索", |     "placeholder": "快速搜索", | ||||||
|     "searching": "正在搜索...", |     "searching": "正在搜索...", | ||||||
|   | |||||||
| @@ -1449,9 +1449,6 @@ | |||||||
|     "title": "Hervorhebungs-Liste", |     "title": "Hervorhebungs-Liste", | ||||||
|     "options": "Optionen" |     "options": "Optionen" | ||||||
|   }, |   }, | ||||||
|   "mermaid": { |  | ||||||
|     "diagram_error": "Das Diagramm konnte nicht angezeigt werden. Siehe <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">Hilfe und Beispiele</a>." |  | ||||||
|   }, |  | ||||||
|   "quick-search": { |   "quick-search": { | ||||||
|     "placeholder": "Schnellsuche", |     "placeholder": "Schnellsuche", | ||||||
|     "searching": "Suche läuft…", |     "searching": "Suche läuft…", | ||||||
|   | |||||||
| @@ -1465,9 +1465,6 @@ | |||||||
|     "title": "Highlights List", |     "title": "Highlights List", | ||||||
|     "options": "Options" |     "options": "Options" | ||||||
|   }, |   }, | ||||||
|   "mermaid": { |  | ||||||
|     "diagram_error": "The diagram could not be displayed. See <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">help and examples</a>." |  | ||||||
|   }, |  | ||||||
|   "quick-search": { |   "quick-search": { | ||||||
|     "placeholder": "Quick search", |     "placeholder": "Quick search", | ||||||
|     "searching": "Searching...", |     "searching": "Searching...", | ||||||
| @@ -1703,5 +1700,13 @@ | |||||||
|   "content_language": { |   "content_language": { | ||||||
|     "title": "Content languages", |     "title": "Content languages", | ||||||
|     "description": "Select one or more languages that should appear in the language selection in the Basic Properties section of a read-only or editable text note. This will allow features such as spell-checking or right-to-left support." |     "description": "Select one or more languages that should appear in the language selection in the Basic Properties section of a read-only or editable text note. This will allow features such as spell-checking or right-to-left support." | ||||||
|  |   }, | ||||||
|  |   "switch_layout_button": { | ||||||
|  |     "title_vertical": "Move editing pane to the bottom", | ||||||
|  |     "title_horizontal": "Move editing pane to the left" | ||||||
|  |   }, | ||||||
|  |   "toggle_read_only_button": { | ||||||
|  |     "unlock-editing": "Unlock editing", | ||||||
|  |     "lock-editing": "Lock editing" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1455,9 +1455,6 @@ | |||||||
|     "title": "Lista de destacados", |     "title": "Lista de destacados", | ||||||
|     "options": "Opciones" |     "options": "Opciones" | ||||||
|   }, |   }, | ||||||
|   "mermaid": { |  | ||||||
|     "diagram_error": "El diagrama no pudo ser mostrado. Vea <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">ayuda y ejemplos</a>." |  | ||||||
|   }, |  | ||||||
|   "quick-search": { |   "quick-search": { | ||||||
|     "placeholder": "Búsqueda rápida", |     "placeholder": "Búsqueda rápida", | ||||||
|     "searching": "Buscando...", |     "searching": "Buscando...", | ||||||
|   | |||||||
| @@ -1455,9 +1455,6 @@ | |||||||
|     "title": "Accentuations", |     "title": "Accentuations", | ||||||
|     "options": "Options" |     "options": "Options" | ||||||
|   }, |   }, | ||||||
|   "mermaid": { |  | ||||||
|     "diagram_error": "Le diagramme n'a pas pu être affiché. Consultez l'<a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">aide et exemples</a>." |  | ||||||
|   }, |  | ||||||
|   "quick-search": { |   "quick-search": { | ||||||
|     "placeholder": "Recherche rapide", |     "placeholder": "Recherche rapide", | ||||||
|     "searching": "Recherche...", |     "searching": "Recherche...", | ||||||
|   | |||||||
| @@ -1428,9 +1428,6 @@ | |||||||
|     "options": "Setări", |     "options": "Setări", | ||||||
|     "title": "Listă de evidențieri" |     "title": "Listă de evidențieri" | ||||||
|   }, |   }, | ||||||
|   "mermaid": { |  | ||||||
|     "diagram_error": "Diagrama nu a putut fi afișată. Vedeți <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">informații și exemple pe site-ul oficial</a>." |  | ||||||
|   }, |  | ||||||
|   "note_icon": { |   "note_icon": { | ||||||
|     "change_note_icon": "Schimbă iconița notiței", |     "change_note_icon": "Schimbă iconița notiței", | ||||||
|     "category": "Categorie:", |     "category": "Categorie:", | ||||||
|   | |||||||
| @@ -1399,9 +1399,6 @@ | |||||||
|     "title": "高亮列表", |     "title": "高亮列表", | ||||||
|     "options": "選項" |     "options": "選項" | ||||||
|   }, |   }, | ||||||
|   "mermaid": { |  | ||||||
|     "diagram_error": "圖表無法顯示。 請參考 <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">幫助文檔和示例</a>。" |  | ||||||
|   }, |  | ||||||
|   "quick-search": { |   "quick-search": { | ||||||
|     "placeholder": "快速搜尋", |     "placeholder": "快速搜尋", | ||||||
|     "searching": "正在搜尋...", |     "searching": "正在搜尋...", | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import { listSyntaxHighlightingThemes } from "../../services/code_block_theme.js | |||||||
| import type { OptionNames } from "../../services/options_interface.js"; | import type { OptionNames } from "../../services/options_interface.js"; | ||||||
|  |  | ||||||
| // options allowed to be updated directly in the Options dialog | // options allowed to be updated directly in the Options dialog | ||||||
| const ALLOWED_OPTIONS = new Set([ | const ALLOWED_OPTIONS = new Set<OptionNames>([ | ||||||
|     "eraseEntitiesAfterTimeInSeconds", |     "eraseEntitiesAfterTimeInSeconds", | ||||||
|     "eraseEntitiesAfterTimeScale", |     "eraseEntitiesAfterTimeScale", | ||||||
|     "protectedSessionTimeout", |     "protectedSessionTimeout", | ||||||
| @@ -78,7 +78,8 @@ const ALLOWED_OPTIONS = new Set([ | |||||||
|     "backgroundEffects", |     "backgroundEffects", | ||||||
|     "allowedHtmlTags", |     "allowedHtmlTags", | ||||||
|     "redirectBareDomain", |     "redirectBareDomain", | ||||||
|     "showLoginInShareTheme" |     "showLoginInShareTheme", | ||||||
|  |     "splitEditorOrientation" | ||||||
| ]); | ]); | ||||||
|  |  | ||||||
| function getOptions() { | function getOptions() { | ||||||
| @@ -163,7 +164,10 @@ function getSupportedLocales() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function isAllowed(name: string) { | function isAllowed(name: string) { | ||||||
|     return ALLOWED_OPTIONS.has(name) || name.startsWith("keyboardShortcuts") || name.endsWith("Collapsed") || name.startsWith("hideArchivedNotes"); |     return (ALLOWED_OPTIONS as Set<string>).has(name) | ||||||
|  |         || name.startsWith("keyboardShortcuts") | ||||||
|  |         || name.endsWith("Collapsed") | ||||||
|  |         || name.startsWith("hideArchivedNotes"); | ||||||
| } | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   | |||||||
| @@ -136,6 +136,9 @@ const defaultOptions: DefaultOption[] = [ | |||||||
|     { name: 'userSubjectIdentifierSaved', value: 'false', isSynced: true }, |     { name: 'userSubjectIdentifierSaved', value: 'false', isSynced: true }, | ||||||
|     { name: 'oAuthEnabled', value: 'false', isSynced: true }, |     { name: 'oAuthEnabled', value: 'false', isSynced: true }, | ||||||
|  |  | ||||||
|  |     // Appearance | ||||||
|  |     { name: "splitEditorOrientation", value: "horizontal", isSynced: true }, | ||||||
|  |  | ||||||
|     // Internationalization |     // Internationalization | ||||||
|     { name: "locale", value: "en", isSynced: true }, |     { name: "locale", value: "en", isSynced: true }, | ||||||
|     { name: "firstDayOfWeek", value: "1", isSynced: true }, |     { name: "firstDayOfWeek", value: "1", isSynced: true }, | ||||||
|   | |||||||
| @@ -49,6 +49,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi | |||||||
|     encryptedRecoveryCodes: boolean; |     encryptedRecoveryCodes: boolean; | ||||||
|     userSubjectIdentifierSaved: boolean; |     userSubjectIdentifierSaved: boolean; | ||||||
|     oAuthEnabled: boolean; |     oAuthEnabled: boolean; | ||||||
|  |     hoistedNoteId: string; | ||||||
|  |  | ||||||
|     lastSyncedPull: number; |     lastSyncedPull: number; | ||||||
|     lastSyncedPush: number; |     lastSyncedPush: number; | ||||||
| @@ -77,6 +78,9 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi | |||||||
|     firstDayOfWeek: number; |     firstDayOfWeek: number; | ||||||
|     languages: string; |     languages: string; | ||||||
|  |  | ||||||
|  |     // Appearance | ||||||
|  |     splitEditorOrientation: "horziontal" | "vertical"; | ||||||
|  |  | ||||||
|     initialized: boolean; |     initialized: boolean; | ||||||
|     isPasswordSet: boolean; |     isPasswordSet: boolean; | ||||||
|     overrideThemeFonts: boolean; |     overrideThemeFonts: boolean; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user