From cf874b5ee89ef0424a29877a0598c5e6af28ef9f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Mar 2025 10:27:42 +0200 Subject: [PATCH] feat(mermaid): add basic support for vertical layout --- src/public/app/layouts/desktop_layout.ts | 2 + .../floating_buttons/switch_layout_button.ts | 38 +++++++++++++++++++ .../abstract_split_type_widget.ts | 31 +++++++++++++-- src/routes/api/options.ts | 10 +++-- src/services/options_init.ts | 3 ++ src/services/options_interface.ts | 4 ++ 6 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 src/public/app/widgets/floating_buttons/switch_layout_button.ts diff --git a/src/public/app/layouts/desktop_layout.ts b/src/public/app/layouts/desktop_layout.ts index 945be3e5e..859f9dc04 100644 --- a/src/public/app/layouts/desktop_layout.ts +++ b/src/public/app/layouts/desktop_layout.ts @@ -89,6 +89,7 @@ import ContextualHelpButton from "../widgets/floating_buttons/help_button.js"; import CloseZenButton from "../widgets/close_zen_button.js"; import type { AppContext } from "./../components/app_context.js"; import type { WidgetsByParent } from "../services/bundle.js"; +import SwitchSplitOrientationButton from "../widgets/floating_buttons/switch_layout_button.js"; export default class DesktopLayout { @@ -202,6 +203,7 @@ export default class DesktopLayout { .child(new WatchedFileUpdateStatusWidget()) .child( new FloatingButtons() + .child(new SwitchSplitOrientationButton()) .child(new EditButton()) .child(new ShowTocWidgetButton()) .child(new ShowHighlightsListWidgetButton()) diff --git a/src/public/app/widgets/floating_buttons/switch_layout_button.ts b/src/public/app/widgets/floating_buttons/switch_layout_button.ts new file mode 100644 index 000000000..6dc2cfde2 --- /dev/null +++ b/src/public/app/widgets/floating_buttons/switch_layout_button.ts @@ -0,0 +1,38 @@ +import options from "../../services/options.js"; +import NoteContextAwareWidget from "../note_context_aware_widget.js"; + +const TPL = ` + +`; + +export default class SwitchSplitOrientationButton extends NoteContextAwareWidget { + isEnabled() { + return super.isEnabled() + && ["mermaid"].includes(this.note?.type ?? "") + && this.note?.isContentAvailable() + && 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.contentSized(); + } +} + +function toggleOrientation(orientation: string) { + if (orientation === "horizontal") { + return "vertical"; + } else { + return "horizontal"; + } +} diff --git a/src/public/app/widgets/type_widgets/abstract_split_type_widget.ts b/src/public/app/widgets/type_widgets/abstract_split_type_widget.ts index aad7d89ee..01be1cc26 100644 --- a/src/public/app/widgets/type_widgets/abstract_split_type_widget.ts +++ b/src/public/app/widgets/type_widgets/abstract_split_type_widget.ts @@ -4,9 +4,11 @@ import EditableCodeTypeWidget from "./editable_code.js"; import TypeWidget from "./type_widget.js"; import Split from "split.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"; const TPL = `\ -
+
@@ -37,6 +39,7 @@ const TPL = `\ .note-detail-split .note-detail-split-preview { transition: opacity 250ms ease-in-out; + height: 100%; } .note-detail-split .note-detail-split-preview.on-error { @@ -58,13 +61,28 @@ const TPL = `\ height: 100%; } - .note-detail-split-first-col { + .note-detail-split.split-horizontal .note-detail-split-first-col { flex-direction: column; } /* Vertical layout */ + .note-detail-split.split-vertical { + flex-direction: column; + } + .note-detail-split.split-vertical > div { + width: 100%; + height: 50%; + } + + .note-detail-split.split-vertical > .note-detail-split-first-col { + border-top: 1px solid var(--main-border-color); + } + + .note-detail-split.split-vertical .note-detail-split-second-col { + order: -1; + }
`; @@ -76,6 +94,7 @@ const TPL = `\ * * - 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}. + * - Horizontal or vertical orientation for the editor/preview split, adjustable via {@link SwitchSplitOrientationButton}. */ export default abstract class AbstractSplitTypeWidget extends TypeWidget { @@ -87,6 +106,7 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { private $editor!: JQuery; private $errorContainer!: JQuery; private editorTypeWidget: EditableCodeTypeWidget; + private layoutOrientation!: "horizontal" | "vertical"; constructor() { super(); @@ -98,6 +118,10 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { doRender(): void { this.$widget = $(TPL); + const layoutOrientation = options.get("splitEditorOrientation") ?? "horizontal"; + this.$widget.addClass(`split-${layoutOrientation}`); + this.layoutOrientation = layoutOrientation as ("horizontal" | "vertical"); + this.$firstCol = this.$widget.find(".note-detail-split-first-col"); this.$secondCol = this.$widget.find(".note-detail-split-second-col"); this.$preview = this.$widget.find(".note-detail-split-preview"); @@ -132,7 +156,7 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { this.splitInstance?.destroy(); this.splitInstance = Split([ this.$firstCol[0], this.$secondCol[0] ], { sizes: [ 50, 50 ], - direction: "horizontal", + direction: this.layoutOrientation, gutterSize: DEFAULT_GUTTER_SIZE, ...this.buildSplitExtraOptions() }); @@ -163,4 +187,5 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget { getData() { return this.editorTypeWidget.getData(); } + } diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts index 1a52d75fa..d4f480615 100644 --- a/src/routes/api/options.ts +++ b/src/routes/api/options.ts @@ -10,7 +10,7 @@ import { listSyntaxHighlightingThemes } from "../../services/code_block_theme.js import type { OptionNames } from "../../services/options_interface.js"; // options allowed to be updated directly in the Options dialog -const ALLOWED_OPTIONS = new Set([ +const ALLOWED_OPTIONS = new Set([ "eraseEntitiesAfterTimeInSeconds", "eraseEntitiesAfterTimeScale", "protectedSessionTimeout", @@ -78,7 +78,8 @@ const ALLOWED_OPTIONS = new Set([ "backgroundEffects", "allowedHtmlTags", "redirectBareDomain", - "showLoginInShareTheme" + "showLoginInShareTheme", + "splitEditorOrientation" ]); function getOptions() { @@ -163,7 +164,10 @@ function getSupportedLocales() { } function isAllowed(name: string) { - return ALLOWED_OPTIONS.has(name) || name.startsWith("keyboardShortcuts") || name.endsWith("Collapsed") || name.startsWith("hideArchivedNotes"); + return (ALLOWED_OPTIONS as Set).has(name) + || name.startsWith("keyboardShortcuts") + || name.endsWith("Collapsed") + || name.startsWith("hideArchivedNotes"); } export default { diff --git a/src/services/options_init.ts b/src/services/options_init.ts index a3bb95c76..e08833820 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -132,6 +132,9 @@ const defaultOptions: DefaultOption[] = [ { name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true }, { name: "editedNotesOpenInRibbon", value: "true", isSynced: true }, + // Appearance + { name: "splitEditorOrientation", value: "horizontal", isSynced: true }, + // Internationalization { name: "locale", value: "en", isSynced: true }, { name: "firstDayOfWeek", value: "1", isSynced: true }, diff --git a/src/services/options_interface.ts b/src/services/options_interface.ts index 51b289d4d..f7241956b 100644 --- a/src/services/options_interface.ts +++ b/src/services/options_interface.ts @@ -45,6 +45,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions