mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-30 11:42:26 +08:00
Merge branch 'develop' into renovate/apple-actions-import-codesign-certs-4.x
This commit is contained in:
commit
25c66e7c57
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
8
package-lock.json
generated
8
package-lock.json
generated
@ -142,7 +142,7 @@
|
|||||||
"@types/leaflet-gpx": "1.3.7",
|
"@types/leaflet-gpx": "1.3.7",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/multer": "1.4.12",
|
"@types/multer": "1.4.12",
|
||||||
"@types/node": "22.13.10",
|
"@types/node": "22.13.11",
|
||||||
"@types/react": "18.3.19",
|
"@types/react": "18.3.19",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "18.3.5",
|
||||||
"@types/safe-compare": "1.1.2",
|
"@types/safe-compare": "1.1.2",
|
||||||
@ -5925,9 +5925,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.10",
|
"version": "22.13.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
|
||||||
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
|
"integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
|
@ -199,7 +199,7 @@
|
|||||||
"@types/leaflet-gpx": "1.3.7",
|
"@types/leaflet-gpx": "1.3.7",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/multer": "1.4.12",
|
"@types/multer": "1.4.12",
|
||||||
"@types/node": "22.13.10",
|
"@types/node": "22.13.11",
|
||||||
"@types/react": "18.3.19",
|
"@types/react": "18.3.19",
|
||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "18.3.5",
|
||||||
"@types/safe-compare": "1.1.2",
|
"@types/safe-compare": "1.1.2",
|
||||||
|
@ -343,9 +343,8 @@ type EventMappings = {
|
|||||||
noteContextRemoved: {
|
noteContextRemoved: {
|
||||||
ntxIds: string[];
|
ntxIds: string[];
|
||||||
};
|
};
|
||||||
exportSvg: {
|
exportSvg: { ntxId: string | null | undefined; };
|
||||||
ntxId: string | null | undefined;
|
exportPng: { ntxId: string | null | undefined; };
|
||||||
};
|
|
||||||
geoMapCreateChildNote: {
|
geoMapCreateChildNote: {
|
||||||
ntxId: string | null | undefined; // TODO: deduplicate ntxId
|
ntxId: string | null | undefined; // TODO: deduplicate ntxId
|
||||||
};
|
};
|
||||||
|
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,9 @@ 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";
|
||||||
|
import PngExportButton from "../widgets/floating_buttons/png_export_button.js";
|
||||||
|
|
||||||
export default class DesktopLayout {
|
export default class DesktopLayout {
|
||||||
|
|
||||||
@ -202,6 +205,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())
|
||||||
@ -210,6 +215,7 @@ export default class DesktopLayout {
|
|||||||
.child(new GeoMapButtons())
|
.child(new GeoMapButtons())
|
||||||
.child(new CopyImageReferenceButton())
|
.child(new CopyImageReferenceButton())
|
||||||
.child(new SvgExportButton())
|
.child(new SvgExportButton())
|
||||||
|
.child(new PngExportButton())
|
||||||
.child(new BacklinksWidget())
|
.child(new BacklinksWidget())
|
||||||
.child(new ContextualHelpButton())
|
.child(new ContextualHelpButton())
|
||||||
.child(new HideFloatingButtonsButton())
|
.child(new HideFloatingButtonsButton())
|
||||||
|
@ -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.
|
||||||
|
@ -609,9 +609,20 @@ function createImageSrcUrl(note: { noteId: string; title: string }) {
|
|||||||
*/
|
*/
|
||||||
function downloadSvg(nameWithoutExtension: string, svgContent: string) {
|
function downloadSvg(nameWithoutExtension: string, svgContent: string) {
|
||||||
const filename = `${nameWithoutExtension}.svg`;
|
const filename = `${nameWithoutExtension}.svg`;
|
||||||
|
const dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
|
||||||
|
triggerDownload(filename, dataUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the given data URL on the client device, with a custom file name.
|
||||||
|
*
|
||||||
|
* @param fileName the name to give the downloaded file.
|
||||||
|
* @param dataUrl the data URI to download.
|
||||||
|
*/
|
||||||
|
function triggerDownload(fileName: string, dataUrl: string) {
|
||||||
const element = document.createElement("a");
|
const element = document.createElement("a");
|
||||||
element.setAttribute("href", `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`);
|
element.setAttribute("href", dataUrl);
|
||||||
element.setAttribute("download", filename);
|
element.setAttribute("download", fileName);
|
||||||
|
|
||||||
element.style.display = "none";
|
element.style.display = "none";
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
@ -621,6 +632,56 @@ function downloadSvg(nameWithoutExtension: string, svgContent: string) {
|
|||||||
document.body.removeChild(element);
|
document.body.removeChild(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string representation of an SVG, renders the SVG to PNG and triggers a download of the file on the client device.
|
||||||
|
*
|
||||||
|
* Note that the SVG must specify its width and height as attributes in order for it to be rendered.
|
||||||
|
*
|
||||||
|
* @param nameWithoutExtension the name of the file. The .png suffix is automatically added to it.
|
||||||
|
* @param svgContent the content of the SVG file download.
|
||||||
|
* @returns `true` if the operation succeeded (width/height present), or `false` if the download was not triggered.
|
||||||
|
*/
|
||||||
|
function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) {
|
||||||
|
const mime = "image/svg+xml";
|
||||||
|
|
||||||
|
// First, we need to determine the width and the height from the input SVG.
|
||||||
|
const svgDocument = (new DOMParser()).parseFromString(svgContent, mime);
|
||||||
|
const width = svgDocument.documentElement?.getAttribute("width");
|
||||||
|
const height = svgDocument.documentElement?.getAttribute("height");
|
||||||
|
|
||||||
|
if (!width || !height) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the image to a blob.
|
||||||
|
const svgBlob = new Blob([ svgContent ], {
|
||||||
|
type: mime
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create an image element and load the SVG.
|
||||||
|
const imageEl = new Image();
|
||||||
|
imageEl.width = parseFloat(width);
|
||||||
|
imageEl.height = parseFloat(height);
|
||||||
|
imageEl.src = URL.createObjectURL(svgBlob);
|
||||||
|
imageEl.onload = () => {
|
||||||
|
// Draw the image with a canvas.
|
||||||
|
const canvasEl = document.createElement("canvas");
|
||||||
|
canvasEl.width = imageEl.width;
|
||||||
|
canvasEl.height = imageEl.height;
|
||||||
|
document.body.appendChild(canvasEl);
|
||||||
|
|
||||||
|
const ctx = canvasEl.getContext("2d");
|
||||||
|
ctx?.drawImage(imageEl, 0, 0);
|
||||||
|
URL.revokeObjectURL(imageEl.src);
|
||||||
|
|
||||||
|
const imgUri = canvasEl.toDataURL("image/png")
|
||||||
|
triggerDownload(`${nameWithoutExtension}.png`, imgUri);
|
||||||
|
document.body.removeChild(canvasEl);
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two semantic version strings.
|
* Compares two semantic version strings.
|
||||||
* Returns:
|
* Returns:
|
||||||
@ -719,6 +780,7 @@ export default {
|
|||||||
copyHtmlToClipboard,
|
copyHtmlToClipboard,
|
||||||
createImageSrcUrl,
|
createImageSrcUrl,
|
||||||
downloadSvg,
|
downloadSvg,
|
||||||
|
downloadSvgAsPng,
|
||||||
compareVersions,
|
compareVersions,
|
||||||
isUpdateAvailable,
|
isUpdateAvailable,
|
||||||
isLaunchBarConfig
|
isLaunchBarConfig
|
||||||
|
13
src/public/app/types.d.ts
vendored
13
src/public/app/types.d.ts
vendored
@ -198,12 +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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
24
src/public/app/widgets/floating_buttons/png_export_button.ts
Normal file
24
src/public/app/widgets/floating_buttons/png_export_button.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { t } from "../../services/i18n.js";
|
||||||
|
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||||
|
|
||||||
|
const TPL = `
|
||||||
|
<button type="button"
|
||||||
|
class="export-svg-button"
|
||||||
|
title="${t("png_export_button.button_title")}">
|
||||||
|
<span class="bx bxs-file-png"></span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default class PngExportButton extends NoteContextAwareWidget {
|
||||||
|
isEnabled() {
|
||||||
|
return super.isEnabled() && ["mermaid", "mindMap"].includes(this.note?.type ?? "") && this.note?.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
doRender() {
|
||||||
|
super.doRender();
|
||||||
|
|
||||||
|
this.$widget = $(TPL);
|
||||||
|
this.$widget.on("click", () => this.triggerEvent("exportPng", { ntxId: this.ntxId }));
|
||||||
|
this.contentSized();
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ const TPL = `
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
class="export-svg-button"
|
class="export-svg-button"
|
||||||
title="${t("svg_export_button.button_title")}">
|
title="${t("svg_export_button.button_title")}">
|
||||||
<span class="bx bx-export"></span>
|
<span class="bx bxs-file-image"></span>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
@ -30,13 +40,20 @@ const TPL = `\
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-detail-split .note-detail-split-editor .note-detail-code {
|
||||||
|
contain: size !important;
|
||||||
|
}
|
||||||
|
|
||||||
.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 +62,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 +76,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 +112,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,44 +137,99 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup(): void {
|
cleanup(): void {
|
||||||
this.splitInstance?.destroy();
|
this.#destroyResizer();
|
||||||
this.splitInstance = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = (!utils.isMobile() ? options.get("splitEditorOrientation") ?? "horizontal" : "vertical");
|
||||||
|
if (this.layoutOrientation !== layoutOrientation || this.isReadOnly !== isReadOnly) {
|
||||||
|
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.#destroyResizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.splitInstance) {
|
||||||
|
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] ], {
|
|
||||||
sizes: [ 50, 50 ],
|
if (!this.isReadOnly) {
|
||||||
direction: "horizontal",
|
this.splitInstance = Split(elements, {
|
||||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
sizes: [ 50, 50 ],
|
||||||
...this.buildSplitExtraOptions()
|
direction: this.layoutOrientation,
|
||||||
});
|
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||||
|
...this.buildSplitExtraOptions()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.splitInstance = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#destroyResizer() {
|
||||||
|
this.splitInstance?.destroy();
|
||||||
|
this.splitInstance = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,6 +248,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 +261,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,24 +81,22 @@ 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);
|
||||||
|
|
||||||
|
// Rendering was succesful.
|
||||||
|
this.setError(null);
|
||||||
|
|
||||||
|
if (svg === this.svg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.svg = svg;
|
||||||
|
this.$renderContainer.html(svg);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
// Rendering failed.
|
// Rendering failed.
|
||||||
this.setError((e as Error)?.message);
|
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);
|
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();
|
||||||
@ -187,4 +217,12 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
|||||||
utils.downloadSvg(this.note.title, this.svg);
|
utils.downloadSvg(this.note.title, this.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportPngEvent({ ntxId }: EventData<"exportPng">) {
|
||||||
|
if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.downloadSvgAsPng(this.note.title, this.svg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -276,4 +276,14 @@ export default class MindMapWidget extends TypeWidget {
|
|||||||
const svg = await this.renderSvg();
|
const svg = await this.renderSvg();
|
||||||
utils.downloadSvg(this.note.title, svg);
|
utils.downloadSvg(this.note.title, svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportPngEvent({ ntxId }: EventData<"exportPng">) {
|
||||||
|
if (!this.isNoteContext(ntxId) || this.note?.type !== "mindMap") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const svg = await this.renderSvg();
|
||||||
|
utils.downloadSvgAsPng(this.note.title, svg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,16 @@
|
|||||||
"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"
|
||||||
|
},
|
||||||
|
"png_export_button": {
|
||||||
|
"button_title": "Export diagram as PNG"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -744,7 +744,8 @@
|
|||||||
"basic_properties": {
|
"basic_properties": {
|
||||||
"note_type": "Tipo de nota",
|
"note_type": "Tipo de nota",
|
||||||
"editable": "Editable",
|
"editable": "Editable",
|
||||||
"basic_properties": "Propiedades básicas"
|
"basic_properties": "Propiedades básicas",
|
||||||
|
"language": "Idioma"
|
||||||
},
|
},
|
||||||
"book_properties": {
|
"book_properties": {
|
||||||
"view_type": "Tipo de vista",
|
"view_type": "Tipo de vista",
|
||||||
@ -1090,6 +1091,7 @@
|
|||||||
"title": "Ancho del contenido",
|
"title": "Ancho del contenido",
|
||||||
"default_description": "Trilium limita de forma predeterminada el ancho máximo del contenido para mejorar la legibilidad de ventanas maximizadas en pantallas anchas.",
|
"default_description": "Trilium limita de forma predeterminada el ancho máximo del contenido para mejorar la legibilidad de ventanas maximizadas en pantallas anchas.",
|
||||||
"max_width_label": "Ancho máximo del contenido en píxeles",
|
"max_width_label": "Ancho máximo del contenido en píxeles",
|
||||||
|
"max_width_unit": "píxeles",
|
||||||
"apply_changes_description": "Para aplicar cambios en el ancho del contenido, haga clic en",
|
"apply_changes_description": "Para aplicar cambios en el ancho del contenido, haga clic en",
|
||||||
"reload_button": "recargar la interfaz",
|
"reload_button": "recargar la interfaz",
|
||||||
"reload_description": "cambios desde las opciones de apariencia"
|
"reload_description": "cambios desde las opciones de apariencia"
|
||||||
@ -1127,7 +1129,11 @@
|
|||||||
"code_auto_read_only_size": {
|
"code_auto_read_only_size": {
|
||||||
"title": "Tamaño automático de solo lectura",
|
"title": "Tamaño automático de solo lectura",
|
||||||
"description": "El tamaño de nota de solo lectura automático es el tamaño después del cual las notas se mostrarán en modo de solo lectura (por razones de rendimiento).",
|
"description": "El tamaño de nota de solo lectura automático es el tamaño después del cual las notas se mostrarán en modo de solo lectura (por razones de rendimiento).",
|
||||||
"label": "Tamaño automático de solo lectura (notas de código)"
|
"label": "Tamaño automático de solo lectura (notas de código)",
|
||||||
|
"unit": "caracteres"
|
||||||
|
},
|
||||||
|
"code-editor-options": {
|
||||||
|
"title": "Editor"
|
||||||
},
|
},
|
||||||
"code_mime_types": {
|
"code_mime_types": {
|
||||||
"title": "Tipos MIME disponibles en el menú desplegable"
|
"title": "Tipos MIME disponibles en el menú desplegable"
|
||||||
@ -1146,6 +1152,7 @@
|
|||||||
"download_images_description": "El HTML pegado puede contener referencias a imágenes en línea; Trilium encontrará esas referencias y descargará las imágenes para que estén disponibles sin conexión.",
|
"download_images_description": "El HTML pegado puede contener referencias a imágenes en línea; Trilium encontrará esas referencias y descargará las imágenes para que estén disponibles sin conexión.",
|
||||||
"enable_image_compression": "Habilitar la compresión de imágenes",
|
"enable_image_compression": "Habilitar la compresión de imágenes",
|
||||||
"max_image_dimensions": "Ancho/alto máximo de una imagen en píxeles (la imagen cambiará de tamaño si excede esta configuración).",
|
"max_image_dimensions": "Ancho/alto máximo de una imagen en píxeles (la imagen cambiará de tamaño si excede esta configuración).",
|
||||||
|
"max_image_dimensions_unit": "píxeles",
|
||||||
"jpeg_quality_description": "Calidad JPEG (10 - peor calidad, 100 - mejor calidad, se recomienda 50 - 85)"
|
"jpeg_quality_description": "Calidad JPEG (10 - peor calidad, 100 - mejor calidad, se recomienda 50 - 85)"
|
||||||
},
|
},
|
||||||
"attachment_erasure_timeout": {
|
"attachment_erasure_timeout": {
|
||||||
@ -1177,6 +1184,7 @@
|
|||||||
"note_revisions_snapshot_limit_title": "Límite de respaldos de revisiones de nota",
|
"note_revisions_snapshot_limit_title": "Límite de respaldos de revisiones de nota",
|
||||||
"note_revisions_snapshot_limit_description": "El límite de número de respaldos de revisiones de notas se refiere al número máximo de revisiones que pueden guardarse para cada nota. Donde -1 significa sin límite, 0 significa borrar todas las revisiones. Puede establecer el máximo de revisiones para una sola nota a través de la etiqueta #versioningLimit.",
|
"note_revisions_snapshot_limit_description": "El límite de número de respaldos de revisiones de notas se refiere al número máximo de revisiones que pueden guardarse para cada nota. Donde -1 significa sin límite, 0 significa borrar todas las revisiones. Puede establecer el máximo de revisiones para una sola nota a través de la etiqueta #versioningLimit.",
|
||||||
"snapshot_number_limit_label": "Número límite de respaldos de revisiones de nota:",
|
"snapshot_number_limit_label": "Número límite de respaldos de revisiones de nota:",
|
||||||
|
"snapshot_number_limit_unit": "respaldos",
|
||||||
"erase_excess_revision_snapshots": "Eliminar el exceso de respaldos de revisiones ahora",
|
"erase_excess_revision_snapshots": "Eliminar el exceso de respaldos de revisiones ahora",
|
||||||
"erase_excess_revision_snapshots_prompt": "El exceso de respaldos de revisiones han sido eliminadas."
|
"erase_excess_revision_snapshots_prompt": "El exceso de respaldos de revisiones han sido eliminadas."
|
||||||
},
|
},
|
||||||
@ -1219,13 +1227,15 @@
|
|||||||
"table_of_contents": {
|
"table_of_contents": {
|
||||||
"title": "Tabla de contenido",
|
"title": "Tabla de contenido",
|
||||||
"description": "La tabla de contenido aparecerá en las notas de texto cuando la nota tenga más de un número definido de títulos. Puede personalizar este número:",
|
"description": "La tabla de contenido aparecerá en las notas de texto cuando la nota tenga más de un número definido de títulos. Puede personalizar este número:",
|
||||||
|
"unit": "títulos",
|
||||||
"disable_info": "También puede utilizar esta opción para desactivar la TDC (TOC) de forma efectiva estableciendo un número muy alto.",
|
"disable_info": "También puede utilizar esta opción para desactivar la TDC (TOC) de forma efectiva estableciendo un número muy alto.",
|
||||||
"shortcut_info": "Puede configurar un atajo de teclado para alternar rápidamente el panel derecho (incluido el TDC) en Opciones -> Atajos (nombre 'toggleRightPane')."
|
"shortcut_info": "Puede configurar un atajo de teclado para alternar rápidamente el panel derecho (incluido el TDC) en Opciones -> Atajos (nombre 'toggleRightPane')."
|
||||||
},
|
},
|
||||||
"text_auto_read_only_size": {
|
"text_auto_read_only_size": {
|
||||||
"title": "Tamaño para modo de solo lectura automático",
|
"title": "Tamaño para modo de solo lectura automático",
|
||||||
"description": "El tamaño de nota de solo lectura automático es el tamaño después del cual las notas se mostrarán en modo de solo lectura (por razones de rendimiento).",
|
"description": "El tamaño de nota de solo lectura automático es el tamaño después del cual las notas se mostrarán en modo de solo lectura (por razones de rendimiento).",
|
||||||
"label": "Tamaño para modo de solo lectura automático (notas de texto)"
|
"label": "Tamaño para modo de solo lectura automático (notas de texto)",
|
||||||
|
"unit": "caracteres"
|
||||||
},
|
},
|
||||||
"i18n": {
|
"i18n": {
|
||||||
"title": "Localización",
|
"title": "Localización",
|
||||||
@ -1302,7 +1312,7 @@
|
|||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"keyboard_shortcuts": "Atajos de teclado",
|
"keyboard_shortcuts": "Atajos de teclado",
|
||||||
"multiple_shortcuts": "Varios atajos para la misma acción se pueden separar mediante comas.",
|
"multiple_shortcuts": "Varios atajos para la misma acción se pueden separar mediante comas.",
|
||||||
"electron_documentation": "Véa <a href=\"https://www.electronjs.org/docs/latest/api/accelerator\">documentation de Electron </a> para los modificadores y códigos de tecla disponibles.",
|
"electron_documentation": "Véa la <a href=\"https://www.electronjs.org/docs/latest/api/accelerator\">documentación de Electron </a> para los modificadores y códigos de tecla disponibles.",
|
||||||
"type_text_to_filter": "Escriba texto para filtrar los accesos directos...",
|
"type_text_to_filter": "Escriba texto para filtrar los accesos directos...",
|
||||||
"action_name": "Nombre de la acción",
|
"action_name": "Nombre de la acción",
|
||||||
"shortcuts": "Atajos",
|
"shortcuts": "Atajos",
|
||||||
@ -1326,6 +1336,7 @@
|
|||||||
"config_title": "Configuración de sincronización",
|
"config_title": "Configuración de sincronización",
|
||||||
"server_address": "Dirección de la instancia del servidor",
|
"server_address": "Dirección de la instancia del servidor",
|
||||||
"timeout": "Tiempo de espera de sincronización (milisegundos)",
|
"timeout": "Tiempo de espera de sincronización (milisegundos)",
|
||||||
|
"timeout_unit": "milisegundos",
|
||||||
"proxy_label": "Sincronizar servidor proxy (opcional)",
|
"proxy_label": "Sincronizar servidor proxy (opcional)",
|
||||||
"note": "Nota",
|
"note": "Nota",
|
||||||
"note_description": "Si deja la configuración del proxy en blanco, se utilizará el proxy del sistema (se aplica únicamente a la compilación de escritorio/electron).",
|
"note_description": "Si deja la configuración del proxy en blanco, se utilizará el proxy del sistema (se aplica únicamente a la compilación de escritorio/electron).",
|
||||||
@ -1421,8 +1432,7 @@
|
|||||||
"widget": "Widget",
|
"widget": "Widget",
|
||||||
"confirm-change": "No es recomendado cambiar el tipo de nota cuando el contenido de la nota no está vacío. ¿Desea continuar de cualquier manera?",
|
"confirm-change": "No es recomendado cambiar el tipo de nota cuando el contenido de la nota no está vacío. ¿Desea continuar de cualquier manera?",
|
||||||
"geo-map": "Mapa Geo",
|
"geo-map": "Mapa Geo",
|
||||||
"beta-feature": "Beta",
|
"beta-feature": "Beta"
|
||||||
"task-list": "Lista de tareas"
|
|
||||||
},
|
},
|
||||||
"protect_note": {
|
"protect_note": {
|
||||||
"toggle-on": "Proteger la nota",
|
"toggle-on": "Proteger la nota",
|
||||||
@ -1455,9 +1465,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...",
|
||||||
@ -1682,5 +1689,27 @@
|
|||||||
"tomorrow": "Mañana",
|
"tomorrow": "Mañana",
|
||||||
"yesterday": "Ayer"
|
"yesterday": "Ayer"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"content_widget": {
|
||||||
|
"unknown_widget": "Widget desconocido para \"{{id}}\"."
|
||||||
|
},
|
||||||
|
"note_language": {
|
||||||
|
"not_set": "No establecido",
|
||||||
|
"configure-languages": "Configurar idiomas..."
|
||||||
|
},
|
||||||
|
"content_language": {
|
||||||
|
"title": "Contenido de idiomas",
|
||||||
|
"description": "Seleccione uno o más idiomas que deben aparecer en la selección del idioma en la sección Propiedades Básicas de una nota de texto de solo lectura o editable. Esto permitirá características tales como corrección de ortografía o soporte de derecha a izquierda."
|
||||||
|
},
|
||||||
|
"switch_layout_button": {
|
||||||
|
"title_vertical": "Mover el panel de edición hacia abajo",
|
||||||
|
"title_horizontal": "Mover el panel de edición a la izquierda"
|
||||||
|
},
|
||||||
|
"toggle_read_only_button": {
|
||||||
|
"unlock-editing": "Desbloquear la edición",
|
||||||
|
"lock-editing": "Bloquear la edición"
|
||||||
|
},
|
||||||
|
"png_export_button": {
|
||||||
|
"button_title": "Exportar diagrama como PNG"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
17
src/services/export/single.spec.ts
Normal file
17
src/services/export/single.spec.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import BNote from "../../becca/entities/bnote.js";
|
||||||
|
import { mapByNoteType } from "./single.js";
|
||||||
|
|
||||||
|
describe("Note type mappings", () => {
|
||||||
|
it("supports mermaid note", () => {
|
||||||
|
const note = new BNote({
|
||||||
|
type: "mermaid",
|
||||||
|
title: "New note"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mapByNoteType(note, "", "html")).toMatchObject({
|
||||||
|
extension: "mermaid",
|
||||||
|
mime: "text/vnd.mermaid"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -8,6 +8,7 @@ import becca from "../../becca/becca.js";
|
|||||||
import type TaskContext from "../task_context.js";
|
import type TaskContext from "../task_context.js";
|
||||||
import type BBranch from "../../becca/entities/bbranch.js";
|
import type BBranch from "../../becca/entities/bbranch.js";
|
||||||
import type { Response } from "express";
|
import type { Response } from "express";
|
||||||
|
import type BNote from "../../becca/entities/bnote.js";
|
||||||
|
|
||||||
function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response) {
|
function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response) {
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
@ -20,9 +21,21 @@ function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "ht
|
|||||||
return [400, `Unrecognized format '${format}'`];
|
return [400, `Unrecognized format '${format}'`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { payload, extension, mime } = mapByNoteType(note, note.getContent(), format);
|
||||||
|
const fileName = `${note.title}.${extension}`;
|
||||||
|
|
||||||
|
res.setHeader("Content-Disposition", getContentDisposition(fileName));
|
||||||
|
res.setHeader("Content-Type", `${mime}; charset=UTF-8`);
|
||||||
|
|
||||||
|
res.send(payload);
|
||||||
|
|
||||||
|
taskContext.increaseProgressCount();
|
||||||
|
taskContext.taskSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapByNoteType(note: BNote, content: string | Buffer<ArrayBufferLike>, format: "html" | "markdown") {
|
||||||
let payload, extension, mime;
|
let payload, extension, mime;
|
||||||
|
|
||||||
let content = note.getContent();
|
|
||||||
if (typeof content !== "string") {
|
if (typeof content !== "string") {
|
||||||
throw new Error("Unsupported content type for export.");
|
throw new Error("Unsupported content type for export.");
|
||||||
}
|
}
|
||||||
@ -52,21 +65,17 @@ function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "ht
|
|||||||
payload = content;
|
payload = content;
|
||||||
extension = "excalidraw";
|
extension = "excalidraw";
|
||||||
mime = "application/json";
|
mime = "application/json";
|
||||||
|
} else if (note.type === "mermaid") {
|
||||||
|
payload = content;
|
||||||
|
extension = "mermaid";
|
||||||
|
mime = "text/vnd.mermaid";
|
||||||
} else if (note.type === "relationMap" || note.type === "search") {
|
} else if (note.type === "relationMap" || note.type === "search") {
|
||||||
payload = content;
|
payload = content;
|
||||||
extension = "json";
|
extension = "json";
|
||||||
mime = "application/json";
|
mime = "application/json";
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = `${note.title}.${extension}`;
|
return { payload, extension, mime };
|
||||||
|
|
||||||
res.setHeader("Content-Disposition", getContentDisposition(fileName));
|
|
||||||
res.setHeader("Content-Type", `${mime}; charset=UTF-8`);
|
|
||||||
|
|
||||||
res.send(payload);
|
|
||||||
|
|
||||||
taskContext.increaseProgressCount();
|
|
||||||
taskContext.taskSucceeded();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function inlineAttachments(content: string) {
|
function inlineAttachments(content: string) {
|
||||||
|
@ -26,6 +26,15 @@ describe("#getMime", () => {
|
|||||||
["test.excalidraw"], "application/json"
|
["test.excalidraw"], "application/json"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"File extension ('.mermaid') that is defined in EXTENSION_TO_MIME",
|
||||||
|
["test.mermaid"], "text/vnd.mermaid"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"File extension ('.mermaid') that is defined in EXTENSION_TO_MIME",
|
||||||
|
["test.mmd"], "text/vnd.mermaid"
|
||||||
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
"File extension with inconsistent capitalization that is defined in EXTENSION_TO_MIME",
|
"File extension with inconsistent capitalization that is defined in EXTENSION_TO_MIME",
|
||||||
["test.gRoOvY"], "text/x-groovy"
|
["test.gRoOvY"], "text/x-groovy"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import mimeTypes from "mime-types";
|
import mimeTypes from "mime-types";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import type { TaskData } from "../task_context_interface.js";
|
import type { TaskData } from "../task_context_interface.js";
|
||||||
|
import type { NoteType } from "../../becca/entities/rows.js";
|
||||||
|
|
||||||
const CODE_MIME_TYPES = new Set([
|
const CODE_MIME_TYPES = new Set([
|
||||||
"application/json",
|
"application/json",
|
||||||
@ -68,7 +69,9 @@ const EXTENSION_TO_MIME = new Map<string, string>([
|
|||||||
[".scala", "text/x-scala"],
|
[".scala", "text/x-scala"],
|
||||||
[".swift", "text/x-swift"],
|
[".swift", "text/x-swift"],
|
||||||
[".ts", "text/x-typescript"],
|
[".ts", "text/x-typescript"],
|
||||||
[".excalidraw", "application/json"]
|
[".excalidraw", "application/json"],
|
||||||
|
[".mermaid", "text/vnd.mermaid"],
|
||||||
|
[".mmd", "text/vnd.mermaid"]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** @returns false if MIME is not detected */
|
/** @returns false if MIME is not detected */
|
||||||
@ -85,7 +88,7 @@ function getMime(fileName: string) {
|
|||||||
return mimeFromExt || mimeTypes.lookup(fileNameLc);
|
return mimeFromExt || mimeTypes.lookup(fileNameLc);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getType(options: TaskData, mime: string) {
|
function getType(options: TaskData, mime: string): NoteType {
|
||||||
const mimeLc = mime?.toLowerCase();
|
const mimeLc = mime?.toLowerCase();
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
@ -98,6 +101,9 @@ function getType(options: TaskData, mime: string) {
|
|||||||
case mime.startsWith("image/"):
|
case mime.startsWith("image/"):
|
||||||
return "image";
|
return "image";
|
||||||
|
|
||||||
|
case mime === "text/vnd.mermaid":
|
||||||
|
return "mermaid";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "file";
|
return "file";
|
||||||
}
|
}
|
||||||
|
5
src/services/import/samples/New note.mermaid
Normal file
5
src/services/import/samples/New note.mermaid
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
graph TD;
|
||||||
|
A-->B;
|
||||||
|
A-->C;
|
||||||
|
B-->D;
|
||||||
|
C-->D;
|
5
src/services/import/samples/New note.mmd
Normal file
5
src/services/import/samples/New note.mmd
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
graph TD;
|
||||||
|
A-->B;
|
||||||
|
A-->C;
|
||||||
|
B-->D;
|
||||||
|
C-->D;
|
@ -96,4 +96,22 @@ describe("processNoteContent", () => {
|
|||||||
expect(importedNote.type).toBe("canvas");
|
expect(importedNote.type).toBe("canvas");
|
||||||
expect(importedNote.title).toBe("New note");
|
expect(importedNote.title).toBe("New note");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("imports .mermaid as mermaid note", async () => {
|
||||||
|
const { importedNote } = await testImport("New note.mermaid", "application/json");
|
||||||
|
expect(importedNote).toMatchObject({
|
||||||
|
mime: "text/vnd.mermaid",
|
||||||
|
type: "mermaid",
|
||||||
|
title: "New note"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("imports .mmd as mermaid note", async () => {
|
||||||
|
const { importedNote } = await testImport("New note.mmd", "application/json");
|
||||||
|
expect(importedNote).toMatchObject({
|
||||||
|
mime: "text/vnd.mermaid",
|
||||||
|
type: "mermaid",
|
||||||
|
title: "New note"
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -27,6 +27,10 @@ function importSingleFile(taskContext: TaskContext, file: File, parentNote: BNot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mime === "text/vnd.mermaid") {
|
||||||
|
return importCustomType(taskContext, file, parentNote, "mermaid", mime);
|
||||||
|
}
|
||||||
|
|
||||||
if (taskContext?.data?.codeImportedAsCode && mimeService.getType(taskContext.data, mime) === "code") {
|
if (taskContext?.data?.codeImportedAsCode && mimeService.getType(taskContext.data, mime) === "code") {
|
||||||
return importCodeNote(taskContext, file, parentNote);
|
return importCodeNote(taskContext, file, parentNote);
|
||||||
}
|
}
|
||||||
@ -93,6 +97,24 @@ function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote)
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function importCustomType(taskContext: TaskContext, file: File, parentNote: BNote, type: NoteType, mime: string) {
|
||||||
|
const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
|
||||||
|
const content = processStringOrBuffer(file.buffer);
|
||||||
|
|
||||||
|
const { note } = noteService.createNewNote({
|
||||||
|
parentNoteId: parentNote.noteId,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
type,
|
||||||
|
mime: mime,
|
||||||
|
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
|
||||||
|
});
|
||||||
|
|
||||||
|
taskContext.increaseProgressCount();
|
||||||
|
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
function importPlainText(taskContext: TaskContext, file: File, parentNote: BNote) {
|
function importPlainText(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
|
const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
|
||||||
const plainTextContent = processStringOrBuffer(file.buffer);
|
const plainTextContent = processStringOrBuffer(file.buffer);
|
||||||
|
@ -8,7 +8,7 @@ const noteTypes = [
|
|||||||
{ type: "relationMap", defaultMime: "application/json" },
|
{ type: "relationMap", defaultMime: "application/json" },
|
||||||
{ type: "book", defaultMime: "" },
|
{ type: "book", defaultMime: "" },
|
||||||
{ type: "noteMap", defaultMime: "" },
|
{ type: "noteMap", defaultMime: "" },
|
||||||
{ type: "mermaid", defaultMime: "text/plain" },
|
{ type: "mermaid", defaultMime: "text/vnd.mermaid" },
|
||||||
{ type: "canvas", defaultMime: "application/json" },
|
{ type: "canvas", defaultMime: "application/json" },
|
||||||
{ type: "webView", defaultMime: "" },
|
{ type: "webView", defaultMime: "" },
|
||||||
{ type: "launcher", defaultMime: "" },
|
{ type: "launcher", defaultMime: "" },
|
||||||
|
@ -132,6 +132,9 @@ const defaultOptions: DefaultOption[] = [
|
|||||||
{ name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true },
|
{ name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true },
|
||||||
{ name: "editedNotesOpenInRibbon", value: "true", isSynced: true },
|
{ name: "editedNotesOpenInRibbon", value: "true", 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 },
|
||||||
|
@ -45,6 +45,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
|||||||
passwordVerificationSalt: string;
|
passwordVerificationSalt: string;
|
||||||
passwordDerivedKeySalt: string;
|
passwordDerivedKeySalt: string;
|
||||||
encryptedDataKey: string;
|
encryptedDataKey: string;
|
||||||
|
hoistedNoteId: string;
|
||||||
|
|
||||||
lastSyncedPull: number;
|
lastSyncedPull: number;
|
||||||
lastSyncedPush: number;
|
lastSyncedPush: number;
|
||||||
@ -73,6 +74,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;
|
||||||
|
@ -181,6 +181,8 @@ export function removeTextFileExtension(filePath: string) {
|
|||||||
case ".html":
|
case ".html":
|
||||||
case ".htm":
|
case ".htm":
|
||||||
case ".excalidraw":
|
case ".excalidraw":
|
||||||
|
case ".mermaid":
|
||||||
|
case ".mmd":
|
||||||
return filePath.substring(0, filePath.length - extension.length);
|
return filePath.substring(0, filePath.length - extension.length);
|
||||||
default:
|
default:
|
||||||
return filePath;
|
return filePath;
|
||||||
|
@ -244,7 +244,8 @@
|
|||||||
"other": "Otros",
|
"other": "Otros",
|
||||||
"advanced-title": "Avanzado",
|
"advanced-title": "Avanzado",
|
||||||
"visible-launchers-title": "Lanzadores visibles",
|
"visible-launchers-title": "Lanzadores visibles",
|
||||||
"user-guide": "Guía de Usuario"
|
"user-guide": "Guía de Usuario",
|
||||||
|
"localization": "Idioma y Región"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"new-note": "Nueva nota",
|
"new-note": "Nueva nota",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user