mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-10 10:22:29 +08:00
Merge branch 'develop' into feature/MFA
This commit is contained in:
commit
c3d3ab4330
16
libraries/ckeditor/ckeditor-content.css
vendored
16
libraries/ckeditor/ckeditor-content.css
vendored
@ -17,7 +17,7 @@
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ck-content .admonition {
|
||||
.admonition {
|
||||
--accent-color: var(--card-border-color);
|
||||
border: 1px solid var(--accent-color);
|
||||
box-shadow: var(--card-box-shadow);
|
||||
@ -29,19 +29,19 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ck-content .admonition p:last-child {
|
||||
.admonition p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ck-content .admonition p, h2 {
|
||||
.admonition p, h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ck-content .admonition.note { --accent-color: #69c7ff; }
|
||||
.ck-content .admonition.tip { --accent-color: #40c025; }
|
||||
.ck-content .admonition.important { --accent-color: #9839f7; }
|
||||
.ck-content .admonition.caution { --accent-color: #ff2e2e; }
|
||||
.ck-content .admonition.warning { --accent-color: #e2aa03; }
|
||||
.admonition.note { --accent-color: #69c7ff; }
|
||||
.admonition.tip { --accent-color: #40c025; }
|
||||
.admonition.important { --accent-color: #9839f7; }
|
||||
.admonition.caution { --accent-color: #ff2e2e; }
|
||||
.admonition.warning { --accent-color: #e2aa03; }
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
.ck-content .admonition {
|
||||
.admonition {
|
||||
--accent-color: var(--card-border-color);
|
||||
border: 1px solid var(--accent-color);
|
||||
box-shadow: var(--card-box-shadow);
|
||||
@ -29,19 +29,19 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ck-content .admonition p:last-child {
|
||||
.admonition p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ck-content .admonition p, h2 {
|
||||
.admonition p, h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ck-content .admonition.note { --accent-color: #69c7ff; }
|
||||
.ck-content .admonition.tip { --accent-color: #40c025; }
|
||||
.ck-content .admonition.important { --accent-color: #9839f7; }
|
||||
.ck-content .admonition.caution { --accent-color: #ff2e2e; }
|
||||
.ck-content .admonition.warning { --accent-color: #e2aa03; }
|
||||
.admonition.note { --accent-color: #69c7ff; }
|
||||
.admonition.tip { --accent-color: #40c025; }
|
||||
.admonition.important { --accent-color: #9839f7; }
|
||||
.admonition.caution { --accent-color: #ff2e2e; }
|
||||
.admonition.warning { --accent-color: #e2aa03; }
|
||||
|
||||
/*
|
||||
* 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 SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.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 ShowTocWidgetButton from "../widgets/buttons/show_toc_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 type { AppContext } from "./../components/app_context.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 {
|
||||
|
||||
@ -202,6 +204,8 @@ export default class DesktopLayout {
|
||||
.child(new WatchedFileUpdateStatusWidget())
|
||||
.child(
|
||||
new FloatingButtons()
|
||||
.child(new SwitchSplitOrientationButton())
|
||||
.child(new ToggleReadOnlyButton())
|
||||
.child(new EditButton())
|
||||
.child(new ShowTocWidgetButton())
|
||||
.child(new ShowHighlightsListWidgetButton())
|
||||
|
@ -11,7 +11,7 @@ import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session
|
||||
import ConfirmDialog from "../widgets/dialogs/confirm.js";
|
||||
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.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 SvgExportButton from "../widgets/floating_buttons/svg_export_button.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}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* For an attribute with an empty value, pass an empty string instead.
|
||||
@ -90,5 +107,6 @@ export default {
|
||||
setLabel,
|
||||
setAttribute,
|
||||
removeAttributeById,
|
||||
removeOwnedLabelByName,
|
||||
isAffecting
|
||||
};
|
||||
|
@ -1,7 +1,26 @@
|
||||
import type { MermaidConfig } from "mermaid";
|
||||
import type { Mermaid } from "mermaid";
|
||||
|
||||
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
|
||||
* 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;
|
||||
lineWrapping: boolean;
|
||||
keyMap: "vim" | "default";
|
||||
lint: boolean;
|
||||
gutters: string[];
|
||||
tabindex: number;
|
||||
dragDrop: boolean;
|
||||
placeholder: string;
|
||||
readOnly: boolean;
|
||||
keyMap?: "vim" | "default";
|
||||
lint?: boolean;
|
||||
gutters?: string[];
|
||||
tabindex?: number;
|
||||
dragDrop?: boolean;
|
||||
placeholder?: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
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 attributeService from "../../services/attributes.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;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.fixnodes-type-switcher {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
z-index: 10; /* should be below dropdown (note actions) */
|
||||
border-radius: .2rem;
|
||||
}
|
||||
@ -94,14 +79,14 @@ const TPL = `<div class="note-map-widget">
|
||||
|
||||
</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-sitemap tn-tool-button" title="${t("note-map.button-tree-map")}" data-type="tree"></button>
|
||||
</div>
|
||||
|
||||
<! 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>
|
||||
<input type="range" class="slider" min="1" title="${t("note_map.link-distance")}" max="100" value="40" >
|
||||
</div>
|
||||
|
@ -123,7 +123,7 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,20 @@ 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";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import type OnClickButtonWidget from "../buttons/onclick_button.js";
|
||||
|
||||
const TPL = `\
|
||||
<div class="note-detail-split note-detail-printable split-horizontal">
|
||||
<div class="note-detail-split-first-col">
|
||||
<div class="note-detail-split note-detail-printable">
|
||||
<div class="note-detail-split-editor-col">
|
||||
<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 class="note-detail-split-second-col">
|
||||
<div class="note-detail-split-preview-col">
|
||||
<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>
|
||||
|
||||
<style>
|
||||
@ -21,8 +26,13 @@ const TPL = `\
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-detail-split-first-col {
|
||||
.note-detail-split-editor-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.note-detail-split-preview-col {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.note-detail-split .note-detail-split-editor {
|
||||
@ -32,11 +42,14 @@ const TPL = `\
|
||||
|
||||
.note-detail-split .note-detail-error-container {
|
||||
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 {
|
||||
transition: opacity 250ms ease-in-out;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-detail-split .note-detail-split-preview.on-error {
|
||||
@ -45,11 +58,12 @@ const TPL = `\
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
.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%;
|
||||
width: 50%;
|
||||
}
|
||||
@ -58,13 +72,31 @@ const TPL = `\
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-detail-split-first-col {
|
||||
/* Vertical layout */
|
||||
|
||||
.note-detail-split.split-vertical {
|
||||
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>
|
||||
</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}.
|
||||
* - 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 {
|
||||
|
||||
private splitInstance?: Split.Instance;
|
||||
|
||||
protected $preview!: JQuery<HTMLElement>;
|
||||
private $firstCol!: JQuery<HTMLElement>;
|
||||
private $secondCol!: JQuery<HTMLElement>;
|
||||
private $editorCol!: JQuery<HTMLElement>;
|
||||
private $previewCol!: JQuery<HTMLElement>;
|
||||
private $editor!: JQuery<HTMLElement>;
|
||||
private $errorContainer!: JQuery<HTMLElement>;
|
||||
private editorTypeWidget: EditableCodeTypeWidget;
|
||||
private layoutOrientation?: "horizontal" | "vertical";
|
||||
private isReadOnly?: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -98,13 +133,29 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
|
||||
doRender(): void {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$firstCol = this.$widget.find(".note-detail-split-first-col");
|
||||
this.$secondCol = this.$widget.find(".note-detail-split-second-col");
|
||||
// Preview pane
|
||||
this.$previewCol = this.$widget.find(".note-detail-split-preview-col");
|
||||
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.append(this.editorTypeWidget.render());
|
||||
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();
|
||||
}
|
||||
@ -115,27 +166,60 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
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.spacedUpdate = this.spacedUpdate;
|
||||
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() {
|
||||
if (!utils.isDesktop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let elements = [ this.$editorCol[0], this.$previewCol[0] ];
|
||||
if (this.layoutOrientation === "vertical") {
|
||||
elements.reverse();
|
||||
}
|
||||
|
||||
this.splitInstance?.destroy();
|
||||
this.splitInstance = Split([ this.$firstCol[0], this.$secondCol[0] ], {
|
||||
sizes: [ 50, 50 ],
|
||||
direction: "horizontal",
|
||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||
...this.buildSplitExtraOptions()
|
||||
});
|
||||
|
||||
if (!this.isReadOnly) {
|
||||
this.splitInstance = Split(elements, {
|
||||
sizes: [ 50, 50 ],
|
||||
direction: this.layoutOrientation,
|
||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||
...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) {
|
||||
this.$errorContainer.toggleClass("hidden-ext", !message);
|
||||
this.$preview.toggleClass("on-error", !!message);
|
||||
@ -163,4 +251,11 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
|
||||
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 FNote from "../../entities/fnote.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import OnClickButtonWidget from "../buttons/onclick_button.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 content = blob?.content || "";
|
||||
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; } {
|
||||
const data = super.getData();
|
||||
this.onContentChanged(data.content, false);
|
||||
this.#saveSvg();
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -70,24 +81,22 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
||||
let svg: string = "";
|
||||
try {
|
||||
svg = await this.renderSvg(content);
|
||||
|
||||
// Rendering was succesful.
|
||||
this.setError(null);
|
||||
|
||||
if (svg === this.svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.svg = svg;
|
||||
this.$renderContainer.html(svg);
|
||||
} catch (e: unknown) {
|
||||
// Rendering failed.
|
||||
this.setError((e as Error)?.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rendering was succesful.
|
||||
this.setError(null);
|
||||
|
||||
if (svg === this.svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.svg = svg;
|
||||
|
||||
this.$renderContainer.html(svg);
|
||||
await this.#setupPanZoom(!recenter);
|
||||
this.#saveSvg();
|
||||
}
|
||||
|
||||
#saveSvg() {
|
||||
@ -150,7 +159,7 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
||||
const svgPanZoom = (await import("svg-pan-zoom")).default;
|
||||
const zoomInstance = svgPanZoom($svgEl[0], {
|
||||
zoomEnabled: true,
|
||||
controlIconsEnabled: true
|
||||
controlIconsEnabled: false
|
||||
});
|
||||
|
||||
if (preservePanZoom && pan && zoom) {
|
||||
@ -159,6 +168,7 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
||||
zoomInstance.pan(pan);
|
||||
} else {
|
||||
// New instance, reposition properly.
|
||||
zoomInstance.resize();
|
||||
zoomInstance.center();
|
||||
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() {
|
||||
if (this.zoomInstance) {
|
||||
this.zoomInstance.destroy();
|
||||
|
@ -16,7 +16,7 @@ import toast from "../../services/toast.js";
|
||||
import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js";
|
||||
import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { getMermaidConfig } from "./mermaid.js";
|
||||
import { getMermaidConfig } from "../../services/mermaid.js";
|
||||
|
||||
const ENABLE_INSPECTOR = false;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import type { MermaidConfig } from "mermaid";
|
||||
import { loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js";
|
||||
import { getMermaidConfig, loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js";
|
||||
import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js";
|
||||
|
||||
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 { EventData } from "../../components/app_context.js";
|
||||
import { getLocaleById } from "../../services/i18n.js";
|
||||
import { getMermaidConfig } from "./mermaid.js";
|
||||
import { getMermaidConfig } from "../../services/mermaid.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail-readonly-text note-detail-printable">
|
||||
@ -142,7 +142,10 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
// Initialize mermaid
|
||||
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">) {
|
||||
|
@ -1722,7 +1722,7 @@ footer.file-footer button {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.ck-content .admonition {
|
||||
.admonition {
|
||||
--accent-color: var(--card-border-color);
|
||||
border: 1px solid var(--accent-color);
|
||||
box-shadow: var(--card-box-shadow);
|
||||
@ -1735,11 +1735,11 @@ footer.file-footer button {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ck-content .admonition p:last-child {
|
||||
.admonition p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ck-content .admonition::before {
|
||||
.admonition::before {
|
||||
color: var(--accent-color);
|
||||
font-family: boxicons !important;
|
||||
position: absolute;
|
||||
@ -1747,14 +1747,43 @@ footer.file-footer button {
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.ck-content .admonition.note { --accent-color: #69c7ff; }
|
||||
.ck-content .admonition.tip { --accent-color: #40c025; }
|
||||
.ck-content .admonition.important { --accent-color: #9839f7; }
|
||||
.ck-content .admonition.caution { --accent-color: #ff2e2e; }
|
||||
.ck-content .admonition.warning { --accent-color: #e2aa03; }
|
||||
.admonition.note { --accent-color: #69c7ff; }
|
||||
.admonition.tip { --accent-color: #40c025; }
|
||||
.admonition.important { --accent-color: #9839f7; }
|
||||
.admonition.caution { --accent-color: #ff2e2e; }
|
||||
.admonition.warning { --accent-color: #e2aa03; }
|
||||
|
||||
.ck-content .admonition.note::before { content: "\eb21"; }
|
||||
.ck-content .admonition.tip::before { content: "\ea0d"; }
|
||||
.ck-content .admonition.important::before { content: "\ea7c"; }
|
||||
.ck-content .admonition.caution::before { content: "\eac7"; }
|
||||
.ck-content .admonition.warning::before { content: "\eac5"; }
|
||||
.admonition.note::before { content: "\eb21"; }
|
||||
.admonition.tip::before { content: "\ea0d"; }
|
||||
.admonition.important::before { content: "\ea7c"; }
|
||||
.admonition.caution::before { content: "\eac7"; }
|
||||
.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": "高亮列表",
|
||||
"options": "选项"
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram_error": "图表无法显示。 请参考 <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">帮助文档和示例</a>。"
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "快速搜索",
|
||||
"searching": "正在搜索...",
|
||||
|
@ -1449,9 +1449,6 @@
|
||||
"title": "Hervorhebungs-Liste",
|
||||
"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": {
|
||||
"placeholder": "Schnellsuche",
|
||||
"searching": "Suche läuft…",
|
||||
|
@ -1465,9 +1465,6 @@
|
||||
"title": "Highlights List",
|
||||
"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": {
|
||||
"placeholder": "Quick search",
|
||||
"searching": "Searching...",
|
||||
@ -1703,5 +1700,13 @@
|
||||
"content_language": {
|
||||
"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."
|
||||
},
|
||||
"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",
|
||||
"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": {
|
||||
"placeholder": "Búsqueda rápida",
|
||||
"searching": "Buscando...",
|
||||
|
@ -1455,9 +1455,6 @@
|
||||
"title": "Accentuations",
|
||||
"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": {
|
||||
"placeholder": "Recherche rapide",
|
||||
"searching": "Recherche...",
|
||||
|
@ -1428,9 +1428,6 @@
|
||||
"options": "Setări",
|
||||
"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": {
|
||||
"change_note_icon": "Schimbă iconița notiței",
|
||||
"category": "Categorie:",
|
||||
|
@ -1399,9 +1399,6 @@
|
||||
"title": "高亮列表",
|
||||
"options": "選項"
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram_error": "圖表無法顯示。 請參考 <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">幫助文檔和示例</a>。"
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "快速搜尋",
|
||||
"searching": "正在搜尋...",
|
||||
|
@ -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<OptionNames>([
|
||||
"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<string>).has(name)
|
||||
|| name.startsWith("keyboardShortcuts")
|
||||
|| name.endsWith("Collapsed")
|
||||
|| name.startsWith("hideArchivedNotes");
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -136,6 +136,9 @@ const defaultOptions: DefaultOption[] = [
|
||||
{ name: 'userSubjectIdentifierSaved', value: 'false', isSynced: true },
|
||||
{ name: 'oAuthEnabled', value: 'false', isSynced: true },
|
||||
|
||||
// Appearance
|
||||
{ name: "splitEditorOrientation", value: "horizontal", isSynced: true },
|
||||
|
||||
// Internationalization
|
||||
{ name: "locale", value: "en", isSynced: true },
|
||||
{ name: "firstDayOfWeek", value: "1", isSynced: true },
|
||||
|
@ -49,6 +49,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
||||
encryptedRecoveryCodes: boolean;
|
||||
userSubjectIdentifierSaved: boolean;
|
||||
oAuthEnabled: boolean;
|
||||
hoistedNoteId: string;
|
||||
|
||||
lastSyncedPull: number;
|
||||
lastSyncedPush: number;
|
||||
@ -77,6 +78,9 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
||||
firstDayOfWeek: number;
|
||||
languages: string;
|
||||
|
||||
// Appearance
|
||||
splitEditorOrientation: "horziontal" | "vertical";
|
||||
|
||||
initialized: boolean;
|
||||
isPasswordSet: boolean;
|
||||
overrideThemeFonts: boolean;
|
||||
|
Loading…
x
Reference in New Issue
Block a user