From 7957c6d34e92bd796a3391488b8ab057d9973180 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 19 Oct 2024 22:40:27 +0300 Subject: [PATCH 01/60] client: Fix promoted attribute style regressions (closes #503) --- .../ribbon_widgets/promoted_attributes.js | 4 +++ src/public/stylesheets/style.css | 26 +++---------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/public/app/widgets/ribbon_widgets/promoted_attributes.js b/src/public/app/widgets/ribbon_widgets/promoted_attributes.js index 119797c35..5c91b4223 100644 --- a/src/public/app/widgets/ribbon_widgets/promoted_attributes.js +++ b/src/public/app/widgets/ribbon_widgets/promoted_attributes.js @@ -42,6 +42,10 @@ const TPL = ` word-break:keep-all; white-space: nowrap; } + .promoted-attribute-cell input[type="checkbox"] { + height: 1.5em; + } + diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index ac1b90de8..c468f710b 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -70,30 +70,10 @@ textarea, background: var(--input-background-color) url("data:image/svg+xml,") right .75rem center/15px 20px no-repeat; } -/* Hide number input arrows */ -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} - -/* Firefox browser */ -input[type="number"] { - -moz-appearance: textfield; -} - -/* Show number input arrows when focus or hover */ -input[type="number"]:focus::-webkit-inner-spin-button, -input[type="number"]:focus::-webkit-outer-spin-button, -input[type="number"]:hover::-webkit-inner-spin-button, -input[type="number"]:hover::-webkit-outer-spin-button { - -webkit-appearance: inner-spin-button; -} - /* Restore default apperance */ -input[type="number"]:focus, -input[type="number"]:hover { - appearance: auto; +input[type="number"], +input[type="checkbox"] { + appearance: auto !important; } #left-pane input, From cab1d7d353111a70ab0f0a1d14f2c211a0e17aa4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 19 Oct 2024 22:56:45 +0300 Subject: [PATCH 02/60] client: Set up syntax highlight in read-only code (closes #504) --- .../widgets/type_widgets/read_only_code.js | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/type_widgets/read_only_code.js b/src/public/app/widgets/type_widgets/read_only_code.js index 2bcaec195..040996ddb 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.js +++ b/src/public/app/widgets/type_widgets/read_only_code.js @@ -1,4 +1,6 @@ import TypeWidget from "./type_widget.js"; +import libraryLoader from "../../services/library_loader.js"; +import options from "../../services/options.js"; const TPL = `
@@ -21,9 +23,38 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget { doRender() { this.$widget = $(TPL); - this.$content = this.$widget.find('.note-detail-readonly-code-content'); + this.$editor = this.$widget.find('.note-detail-readonly-code-content'); super.doRender(); + + this.initialized = this.initEditor(); + } + + async initEditor() { + await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); + + // these conflict with backward/forward navigation shortcuts + delete CodeMirror.keyMap.default["Alt-Left"]; + delete CodeMirror.keyMap.default["Alt-Right"]; + + CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`; + CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]); + CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"]; + + this.codeEditor = CodeMirror(this.$editor[0], { + value: "", + viewportMargin: Infinity, + indentUnit: 4, + matchBrackets: true, + matchTags: {bothTags: true}, + highlightSelectionMatches: {showToken: false, annotateScrollbar: false}, + gutters: ["CodeMirror-lint-markers"], + lineNumbers: true, + // we line wrap partly also because without it horizontal scrollbar displays only when you scroll + // all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem + lineWrapping: options.is('codeLineWrapEnabled'), + readOnly: true + }); } async doRefresh(note) { @@ -33,7 +64,47 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget { content = this.format(content); } - this.$content.text(content); + // CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check) + // we provide fallback + this.codeEditor.setValue(content || ""); + this.codeEditor.clearHistory(); + + let info = CodeMirror.findModeByMIME(note.mime); + if (!info) { + // Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing. + // To avoid inheriting a mode from a previously open code note. + info = CodeMirror.findModeByMIME("text/plain"); + } + + this.codeEditor.setOption("mode", info.mime); + CodeMirror.autoLoadMode(this.codeEditor, info.mode); + this.show(); + } + + show() { + this.$widget.show(); + + if (this.codeEditor) { // show can be called before render + this.codeEditor.refresh(); + } + } + + focus() { + this.$editor.focus(); + this.codeEditor.focus(); + } + + scrollToEnd() { + this.codeEditor.setCursor(this.codeEditor.lineCount(), 0); + this.codeEditor.focus(); + } + + cleanup() { + if (this.codeEditor) { + this.spacedUpdate.allowUpdateWithoutChange(() => { + this.codeEditor.setValue(''); + }); + } } async executeWithContentElementEvent({resolve, ntxId}) { From c7b7c68a05c9e3702665dc3fa9fa4c598219d470 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 19 Oct 2024 23:12:33 +0300 Subject: [PATCH 03/60] client: Reduce code duplication for CodeMirror --- .../widgets/type_widgets/code_widget_base.js | 89 +++++++++++++++++++ .../app/widgets/type_widgets/editable_code.js | 83 +++-------------- .../widgets/type_widgets/read_only_code.js | 78 ++-------------- 3 files changed, 106 insertions(+), 144 deletions(-) create mode 100644 src/public/app/widgets/type_widgets/code_widget_base.js diff --git a/src/public/app/widgets/type_widgets/code_widget_base.js b/src/public/app/widgets/type_widgets/code_widget_base.js new file mode 100644 index 000000000..5153c1b60 --- /dev/null +++ b/src/public/app/widgets/type_widgets/code_widget_base.js @@ -0,0 +1,89 @@ +import TypeWidget from "./type_widget.js"; +import libraryLoader from "../../services/library_loader.js"; +import options from "../../services/options.js"; + +export default class AbstractCodeTypeWidget extends TypeWidget { + + doRender() { + this.initialized = this.initEditor(); + } + + async initEditor() { + await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); + + // these conflict with backward/forward navigation shortcuts + delete CodeMirror.keyMap.default["Alt-Left"]; + delete CodeMirror.keyMap.default["Alt-Right"]; + + CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`; + CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]); + CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"]; + + this.codeEditor = CodeMirror(this.$editor[0], { + value: "", + viewportMargin: Infinity, + indentUnit: 4, + matchBrackets: true, + matchTags: {bothTags: true}, + highlightSelectionMatches: {showToken: false, annotateScrollbar: false}, + lineNumbers: true, + // we line wrap partly also because without it horizontal scrollbar displays only when you scroll + // all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem + lineWrapping: options.is('codeLineWrapEnabled'), + ...this.getExtraOpts() + }); + this.onEditorInitialized(); + } + + getExtraOpts() { + return {}; + } + + onEditorInitialized() { + + } + + _update(note, content) { + // CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check) + // we provide fallback + this.codeEditor.setValue(content || ""); + this.codeEditor.clearHistory(); + + let info = CodeMirror.findModeByMIME(note.mime); + if (!info) { + // Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing. + // To avoid inheriting a mode from a previously open code note. + info = CodeMirror.findModeByMIME("text/plain"); + } + + this.codeEditor.setOption("mode", info.mime); + CodeMirror.autoLoadMode(this.codeEditor, info.mode); + }; + + show() { + this.$widget.show(); + + if (this.codeEditor) { // show can be called before render + this.codeEditor.refresh(); + } + } + + focus() { + this.$editor.focus(); + this.codeEditor.focus(); + } + + scrollToEnd() { + this.codeEditor.setCursor(this.codeEditor.lineCount(), 0); + this.codeEditor.focus(); + } + + cleanup() { + if (this.codeEditor) { + this.spacedUpdate.allowUpdateWithoutChange(() => { + this.codeEditor.setValue(''); + }); + } + } + +} \ No newline at end of file diff --git a/src/public/app/widgets/type_widgets/editable_code.js b/src/public/app/widgets/type_widgets/editable_code.js index c586830e8..4a70f3962 100644 --- a/src/public/app/widgets/type_widgets/editable_code.js +++ b/src/public/app/widgets/type_widgets/editable_code.js @@ -1,8 +1,7 @@ import { t } from "../../services/i18n.js"; -import libraryLoader from "../../services/library_loader.js"; -import TypeWidget from "./type_widget.js"; import keyboardActionService from "../../services/keyboard_actions.js"; import options from "../../services/options.js"; +import AbstractCodeTypeWidget from "./code_widget_base.js"; const TPL = `
@@ -21,7 +20,7 @@ const TPL = `
`; -export default class EditableCodeTypeWidget extends TypeWidget { +export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget { static getType() { return "editableCode"; } doRender() { @@ -30,44 +29,21 @@ export default class EditableCodeTypeWidget extends TypeWidget { keyboardActionService.setupActionsForElement('code-detail', this.$widget, this); - super.doRender(); - - this.initialized = this.initEditor(); + super.doRender(); } - - async initEditor() { - await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); - - CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; - CodeMirror.keyMap.default["Tab"] = "indentMore"; - - // these conflict with backward/forward navigation shortcuts - delete CodeMirror.keyMap.default["Alt-Left"]; - delete CodeMirror.keyMap.default["Alt-Right"]; - - CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`; - CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]); - CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"]; - - this.codeEditor = CodeMirror(this.$editor[0], { - value: "", - viewportMargin: Infinity, - indentUnit: 4, - matchBrackets: true, + + getExtraOpts() { + return { keyMap: options.is('vimKeymapEnabled') ? "vim": "default", - matchTags: {bothTags: true}, - highlightSelectionMatches: {showToken: false, annotateScrollbar: false}, lint: true, gutters: ["CodeMirror-lint-markers"], - lineNumbers: true, tabindex: 300, - // we line wrap partly also because without it horizontal scrollbar displays only when you scroll - // all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem - lineWrapping: options.is('codeLineWrapEnabled'), dragDrop: false, // with true the editor inlines dropped files which is not what we expect placeholder: t('editable_code.placeholder'), - }); + }; + } + onEditorInitialized() { this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate()); } @@ -75,57 +51,18 @@ export default class EditableCodeTypeWidget extends TypeWidget { const blob = await this.note.getBlob(); await this.spacedUpdate.allowUpdateWithoutChange(() => { - // CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check) - // we provide fallback - this.codeEditor.setValue(blob.content || ""); - this.codeEditor.clearHistory(); - - let info = CodeMirror.findModeByMIME(note.mime); - if (!info) { - // Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing. - // To avoid inheriting a mode from a previously open code note. - info = CodeMirror.findModeByMIME("text/plain"); - } - - this.codeEditor.setOption("mode", info.mime); - CodeMirror.autoLoadMode(this.codeEditor, info.mode); + this._update(note, blob.content); }); this.show(); } - show() { - this.$widget.show(); - - if (this.codeEditor) { // show can be called before render - this.codeEditor.refresh(); - } - } - getData() { return { content: this.codeEditor.getValue() }; } - focus() { - this.$editor.focus(); - this.codeEditor.focus(); - } - - scrollToEnd() { - this.codeEditor.setCursor(this.codeEditor.lineCount(), 0); - this.codeEditor.focus(); - } - - cleanup() { - if (this.codeEditor) { - this.spacedUpdate.allowUpdateWithoutChange(() => { - this.codeEditor.setValue(''); - }); - } - } - async executeWithCodeEditorEvent({resolve, ntxId}) { if (!this.isNoteContext(ntxId)) { return; diff --git a/src/public/app/widgets/type_widgets/read_only_code.js b/src/public/app/widgets/type_widgets/read_only_code.js index 040996ddb..cde96eaa8 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.js +++ b/src/public/app/widgets/type_widgets/read_only_code.js @@ -1,6 +1,4 @@ -import TypeWidget from "./type_widget.js"; -import libraryLoader from "../../services/library_loader.js"; -import options from "../../services/options.js"; +import AbstractCodeTypeWidget from "./code_widget_base.js"; const TPL = `
@@ -18,7 +16,7 @@ const TPL = `

 
`; -export default class ReadOnlyCodeTypeWidget extends TypeWidget { +export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget { static getType() { return "readOnlyCode"; } doRender() { @@ -26,35 +24,6 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget { this.$editor = this.$widget.find('.note-detail-readonly-code-content'); super.doRender(); - - this.initialized = this.initEditor(); - } - - async initEditor() { - await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); - - // these conflict with backward/forward navigation shortcuts - delete CodeMirror.keyMap.default["Alt-Left"]; - delete CodeMirror.keyMap.default["Alt-Right"]; - - CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`; - CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]); - CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"]; - - this.codeEditor = CodeMirror(this.$editor[0], { - value: "", - viewportMargin: Infinity, - indentUnit: 4, - matchBrackets: true, - matchTags: {bothTags: true}, - highlightSelectionMatches: {showToken: false, annotateScrollbar: false}, - gutters: ["CodeMirror-lint-markers"], - lineNumbers: true, - // we line wrap partly also because without it horizontal scrollbar displays only when you scroll - // all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem - lineWrapping: options.is('codeLineWrapEnabled'), - readOnly: true - }); } async doRefresh(note) { @@ -64,47 +33,14 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget { content = this.format(content); } - // CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check) - // we provide fallback - this.codeEditor.setValue(content || ""); - this.codeEditor.clearHistory(); - - let info = CodeMirror.findModeByMIME(note.mime); - if (!info) { - // Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing. - // To avoid inheriting a mode from a previously open code note. - info = CodeMirror.findModeByMIME("text/plain"); - } - - this.codeEditor.setOption("mode", info.mime); - CodeMirror.autoLoadMode(this.codeEditor, info.mode); + this._update(note, content); this.show(); } - show() { - this.$widget.show(); - - if (this.codeEditor) { // show can be called before render - this.codeEditor.refresh(); - } - } - - focus() { - this.$editor.focus(); - this.codeEditor.focus(); - } - - scrollToEnd() { - this.codeEditor.setCursor(this.codeEditor.lineCount(), 0); - this.codeEditor.focus(); - } - - cleanup() { - if (this.codeEditor) { - this.spacedUpdate.allowUpdateWithoutChange(() => { - this.codeEditor.setValue(''); - }); - } + getExtraOpts() { + return { + readOnly: true + }; } async executeWithContentElementEvent({resolve, ntxId}) { From d4956ad3a22d0c73223d6ed3581243aec4c42487 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 19 Oct 2024 23:19:11 +0300 Subject: [PATCH 04/60] client: Refactor and add documentation --- ...t_base.js => abstract_code_type_widget.js} | 37 +++++++++++++++++-- .../app/widgets/type_widgets/editable_code.js | 2 +- .../widgets/type_widgets/read_only_code.js | 2 +- 3 files changed, 36 insertions(+), 5 deletions(-) rename src/public/app/widgets/type_widgets/{code_widget_base.js => abstract_code_type_widget.js} (68%) diff --git a/src/public/app/widgets/type_widgets/code_widget_base.js b/src/public/app/widgets/type_widgets/abstract_code_type_widget.js similarity index 68% rename from src/public/app/widgets/type_widgets/code_widget_base.js rename to src/public/app/widgets/type_widgets/abstract_code_type_widget.js index 5153c1b60..2a54e4cf0 100644 --- a/src/public/app/widgets/type_widgets/code_widget_base.js +++ b/src/public/app/widgets/type_widgets/abstract_code_type_widget.js @@ -2,13 +2,26 @@ import TypeWidget from "./type_widget.js"; import libraryLoader from "../../services/library_loader.js"; import options from "../../services/options.js"; +/** + * An abstract {@link TypeWidget} which implements the CodeMirror editor, meant to be used as a parent for + * widgets requiring the editor. + * + * The widget handles the loading and initialization of the CodeMirror editor, as well as some common + * actions. + * + * The derived class must: + * + * - Define `$editor` in the constructor. + * - Call `super.doRender()` in the extended class. + * - Call `this._update(note, content)` in `#doRefresh(note)`. + */ export default class AbstractCodeTypeWidget extends TypeWidget { doRender() { - this.initialized = this.initEditor(); + this.initialized = this.#initEditor(); } - async initEditor() { + async #initEditor() { await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); // these conflict with backward/forward navigation shortcuts @@ -35,14 +48,32 @@ export default class AbstractCodeTypeWidget extends TypeWidget { this.onEditorInitialized(); } + /** + * Can be extended in derived classes to add extra options to the CodeMirror constructor. The options are appended + * at the end, so it is possible to override the default values introduced by the abstract editor as well. + * + * @returns the extra options to be passed to the CodeMirror constructor. + */ getExtraOpts() { return {}; } + /** + * Called as soon as the CodeMirror library has been loaded and the editor was constructed. Can be extended in + * derived classes to add additional functionality or to register event handlers. + * + * By default, it does nothing. + */ onEditorInitialized() { - + // Do nothing by default. } + /** + * Must be called by the derived classes in `#doRefresh(note)` in order to react to changes. + * + * @param {*} note the note that was changed. + * @param {*} content the new content of the note. + */ _update(note, content) { // CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check) // we provide fallback diff --git a/src/public/app/widgets/type_widgets/editable_code.js b/src/public/app/widgets/type_widgets/editable_code.js index 4a70f3962..452fbbf9b 100644 --- a/src/public/app/widgets/type_widgets/editable_code.js +++ b/src/public/app/widgets/type_widgets/editable_code.js @@ -1,7 +1,7 @@ import { t } from "../../services/i18n.js"; import keyboardActionService from "../../services/keyboard_actions.js"; import options from "../../services/options.js"; -import AbstractCodeTypeWidget from "./code_widget_base.js"; +import AbstractCodeTypeWidget from "./abstract_code_type_widget.js"; const TPL = `
diff --git a/src/public/app/widgets/type_widgets/read_only_code.js b/src/public/app/widgets/type_widgets/read_only_code.js index cde96eaa8..4cc2fb5e1 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.js +++ b/src/public/app/widgets/type_widgets/read_only_code.js @@ -1,4 +1,4 @@ -import AbstractCodeTypeWidget from "./code_widget_base.js"; +import AbstractCodeTypeWidget from "./abstract_code_type_widget.js"; const TPL = `
From 4ad725842ece9525829ccb89220ebe39f537a824 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 00:17:51 +0300 Subject: [PATCH 05/60] server: Trim .htm when importing zip (closes #500) --- src/services/utils.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 0747f744f..990df5004 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -219,11 +219,14 @@ function formatDownloadTitle(fileName: string, type: string | null, mime: string function removeTextFileExtension(filePath: string) { const extension = path.extname(filePath).toLowerCase(); - if (extension === '.md' || extension === '.markdown' || extension === '.html') { - return filePath.substr(0, filePath.length - extension.length); - } - else { - return filePath; + switch (extension) { + case ".md": + case ".markdown": + case ".html": + case ".htm": + return filePath.substr(0, filePath.length - extension.length); + default: + return filePath; } } From 689b3a3079dffd4c7dfaf53786b0ed0ddbd04972 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 00:59:08 +0300 Subject: [PATCH 06/60] i18n: Fix capitalization of no anonymization --- src/public/translations/en/translation.json | 2 +- src/public/translations/es/translation.json | 2 +- src/public/translations/fr/translation.json | 2 +- src/public/translations/ro/translation.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 4255a1f5d..d43331522 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -976,7 +976,7 @@ "error_creating_anonymized_database": "Could not create anonymized database, check backend logs for details", "successfully_created_fully_anonymized_database": "Created fully anonymized database in {{anonymizedFilePath}}", "successfully_created_lightly_anonymized_database": "Created lightly anonymized database in {{anonymizedFilePath}}", - "no_anonymized_database_yet": "no anonymized database yet" + "no_anonymized_database_yet": "No anonymized database yet." }, "database_integrity_check": { "title": "Database Integrity Check", diff --git a/src/public/translations/es/translation.json b/src/public/translations/es/translation.json index aa1e57f73..e2c438475 100644 --- a/src/public/translations/es/translation.json +++ b/src/public/translations/es/translation.json @@ -976,7 +976,7 @@ "error_creating_anonymized_database": "No se pudo crear una base de datos anónima; consulte los registros de backend para obtener más detalles", "successfully_created_fully_anonymized_database": "Se creó una base de datos completamente anónima en {{anonymizedFilePath}}", "successfully_created_lightly_anonymized_database": "Se creó una base de datos ligeramente anónima en {{anonymizedFilePath}}", - "no_anonymized_database_yet": "aún no hay base de datos anónima" + "no_anonymized_database_yet": "Aún no hay base de datos anónima." }, "database_integrity_check": { "title": "Verificación de integridad de la base de datos", diff --git a/src/public/translations/fr/translation.json b/src/public/translations/fr/translation.json index f6b0e7e5f..f95d27bec 100644 --- a/src/public/translations/fr/translation.json +++ b/src/public/translations/fr/translation.json @@ -976,7 +976,7 @@ "error_creating_anonymized_database": "Impossible de créer une base de données anonymisée, vérifiez les journaux backend pour plus de détails", "successfully_created_fully_anonymized_database": "Base de données entièrement anonymisée crée dans {{anonymizedFilePath}}", "successfully_created_lightly_anonymized_database": "Base de données partiellement anonymisée crée dans {{anonymizedFilePath}}", - "no_anonymized_database_yet": "aucune base de données anonymisée" + "no_anonymized_database_yet": "Aucune base de données anonymisée" }, "database_integrity_check": { "title": "Vérification de l'intégrité de la base de données", diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index ac9ee0bf2..a668bab9b 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -381,7 +381,7 @@ "full_anonymization_description": "Această acțiune va crea o nouă copie a bazei de date și o va anonimiza (se șterge conținutul tuturor notițelor și se menține doar structura și câteva metainformații neconfidențiale) pentru a putea fi partajate online cu scopul de a depana anumite probleme fără a risca expunerea datelor personale.", "light_anonymization": "Anonimizare parțială", "light_anonymization_description": "Această acțiune va crea o copie a bazei de date și o va anonimiza parțial - mai exact se va șterge conținutul tuturor notițelor, dar titlurile și atributele vor rămâne. De asemenea, script-urile de front-end sau back-end și widget-urile personalizate vor rămâne și ele. Acest lucru oferă mai mult context pentru a depana probleme.", - "no_anonymized_database_yet": "încă nu există nicio bază de date anonimizată", + "no_anonymized_database_yet": "Încă nu există nicio bază de date anonimizată.", "save_fully_anonymized_database": "Salvează bază de date complet anonimizată", "save_lightly_anonymized_database": "Salvează bază de date parțial anonimizată", "successfully_created_fully_anonymized_database": "S-a creat cu succes o bază de date complet anonimizată în {{anonymizedFilePath}}", From 93efce40237e8389c570dfcd1f2dd3c050a5a8d4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 01:19:02 +0300 Subject: [PATCH 07/60] server: Minimize not found logs (closes #505) --- src/routes/error_handlers.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/routes/error_handlers.ts b/src/routes/error_handlers.ts index f054a2683..f121d31a6 100644 --- a/src/routes/error_handlers.ts +++ b/src/routes/error_handlers.ts @@ -23,13 +23,10 @@ function register(app: Application) { // error handler app.use((err: any, req: Request, res: Response, next: NextFunction) => { - if (err && err.message && ( - (err.message.includes("Router not found for request") && err.message.includes(".js.map")) - || (err.message.includes("Router not found for request") && err.message.includes(".css.map")) - )) { - // ignore - } else { + if (err.status !== 404) { log.info(err); + } else { + log.info(`${err.status} ${req.method} ${req.url}`); } res.status(err.status || 500); From 28f6712a4f87f24e080596198dc7449bbd2bbe7e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 02:06:08 +0300 Subject: [PATCH 08/60] i18n: Translate toast messages --- src/public/app/components/entrypoints.js | 4 +-- src/public/app/menus/tree_context_menu.js | 2 +- src/public/app/services/clipboard.js | 4 +-- src/public/app/services/image.js | 4 +-- src/public/app/services/note_create.js | 2 +- src/public/app/services/protected_session.js | 2 +- src/public/app/services/sync.js | 4 +-- .../app/widgets/buttons/note_actions.js | 4 +-- src/public/app/widgets/note_tree.js | 4 +-- src/public/translations/en/translation.json | 33 ++++++++++++++++--- src/public/translations/ro/translation.json | 33 ++++++++++++++++--- 11 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/public/app/components/entrypoints.js b/src/public/app/components/entrypoints.js index 6a375d5af..3d6c2c2f1 100644 --- a/src/public/app/components/entrypoints.js +++ b/src/public/app/components/entrypoints.js @@ -178,7 +178,7 @@ export default class Entrypoints extends Component { await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results}); } - toastService.showMessage("Note executed"); + toastService.showMessage(t("entrypoints.note-executed")); } hideAllPopups() { @@ -200,6 +200,6 @@ export default class Entrypoints extends Component { await server.post(`notes/${noteId}/revision`); - toastService.showMessage("Note revision has been created."); + toastService.showMessage(t("entrypoints.note-revision-created")); } } diff --git a/src/public/app/menus/tree_context_menu.js b/src/public/app/menus/tree_context_menu.js index caa572816..fbac3321c 100644 --- a/src/public/app/menus/tree_context_menu.js +++ b/src/public/app/menus/tree_context_menu.js @@ -154,7 +154,7 @@ export default class TreeContextMenu { } } - toastService.showMessage(`${converted} notes have been converted to attachments.`); + toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted })); } else if (command === 'copyNotePathToClipboard') { navigator.clipboard.writeText('#' + notePath); diff --git a/src/public/app/services/clipboard.js b/src/public/app/services/clipboard.js index 424784285..4db356816 100644 --- a/src/public/app/services/clipboard.js +++ b/src/public/app/services/clipboard.js @@ -78,7 +78,7 @@ async function copy(branchIds) { clipboard.writeHTML(links.join(', ')); } - toastService.showMessage("Note(s) have been copied into clipboard."); + toastService.showMessage(t("clipboard.copied")); } function cut(branchIds) { @@ -87,7 +87,7 @@ function cut(branchIds) { if (clipboardBranchIds.length > 0) { clipboardMode = 'cut'; - toastService.showMessage("Note(s) have been cut into clipboard."); + toastService.showMessage(t("clipboard.cut")); } } diff --git a/src/public/app/services/image.js b/src/public/app/services/image.js index d3d37ab0e..f732e843c 100644 --- a/src/public/app/services/image.js +++ b/src/public/app/services/image.js @@ -8,9 +8,9 @@ function copyImageReferenceToClipboard($imageWrapper) { const success = document.execCommand('copy'); if (success) { - toastService.showMessage("A reference to the image has been copied to clipboard. This can be pasted in any text note."); + toastService.showMessage(t("image.copied-to-clipboard")); } else { - toastService.showAndLogError("Could not copy the image reference to clipboard."); + toastService.showAndLogError(t("image.cannot-copy")); } } finally { diff --git a/src/public/app/services/note_create.js b/src/public/app/services/note_create.js index 8b8fb1a36..995dec892 100644 --- a/src/public/app/services/note_create.js +++ b/src/public/app/services/note_create.js @@ -119,7 +119,7 @@ async function duplicateSubtree(noteId, parentNotePath) { activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`); const origNote = await froca.getNote(noteId); - toastService.showMessage(`Note "${origNote.title}" has been duplicated`); + toastService.showMessage(t("note_create.duplicated", { title: origNote.title })); } export default { diff --git a/src/public/app/services/protected_session.js b/src/public/app/services/protected_session.js index 9fecf6aeb..24422638d 100644 --- a/src/public/app/services/protected_session.js +++ b/src/public/app/services/protected_session.js @@ -72,7 +72,7 @@ ws.subscribeToMessages(async message => { protectedSessionDeferred = null; } - toastService.showMessage("Protected session has been started."); + toastService.showMessage(t("protected_session.started")); } else if (message.type === 'protectedSessionLogout') { utils.reloadFrontendApp(`Protected session logout`); diff --git a/src/public/app/services/sync.js b/src/public/app/services/sync.js index 6c4f2fc67..5fbac2312 100644 --- a/src/public/app/services/sync.js +++ b/src/public/app/services/sync.js @@ -5,7 +5,7 @@ async function syncNow(ignoreNotConfigured = false) { const result = await server.post('sync/now'); if (result.success) { - toastService.showMessage("Sync finished successfully."); + toastService.showMessage(t("sync.finished-successfully")); } else { if (result.message.length > 200) { @@ -13,7 +13,7 @@ async function syncNow(ignoreNotConfigured = false) { } if (!ignoreNotConfigured || result.errorCode !== 'NOT_CONFIGURED') { - toastService.showError(`Sync failed: ${result.message}`); + toastService.showError(t("sync.failed", { message: result.message })); } } } diff --git a/src/public/app/widgets/buttons/note_actions.js b/src/public/app/widgets/buttons/note_actions.js index 83321458f..dbe324322 100644 --- a/src/public/app/widgets/buttons/note_actions.js +++ b/src/public/app/widgets/buttons/note_actions.js @@ -134,11 +134,11 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { const {attachment: newAttachment} = await server.post(`notes/${this.noteId}/convert-to-attachment`); if (!newAttachment) { - toastService.showMessage(`Converting note '${this.note.title}' failed.`); + toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title })); return; } - toastService.showMessage(`Note '${newAttachment.title}' has been converted to attachment.`); + toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title })); await ws.waitForMaxKnownEntityChangeId(); await appContext.tabManager.getActiveContext().setNote(newAttachment.ownerId, { viewScope: { diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index 1c043ca0d..c79e79ea8 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -1031,7 +1031,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { activeNode.load(true); activeNode.setExpanded(true, {noAnimation: true}); - toastService.showMessage("Saved search note refreshed."); + toastService.showMessage(t("note_tree.saved-search-note-refreshed")); } async batchUpdate(cb) { @@ -1075,7 +1075,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { node.setExpanded(false); if (noneCollapsedYet) { - toastService.showMessage("Auto collapsing notes after inactivity..."); + toastService.showMessage(t("note_tree.auto-collapsing-notes-after-inactivity")); noneCollapsedYet = false; } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index d43331522..fe5990dc5 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -632,7 +632,9 @@ "export_note": "Export note", "delete_note": "Delete note", "print_note": "Print note", - "save_revision": "Save revision" + "save_revision": "Save revision", + "convert_into_attachment_failed": "Converting note '{{title}}' failed.", + "convert_into_attachment_successful": "Note '{{title}' has been converted to attachment." }, "onclick_button": { "no_click_handler": "Button widget '{{componentId}}' has no defined click handler" @@ -921,7 +923,8 @@ }, "protected_session": { "enter_password_instruction": "Showing protected note requires entering your password:", - "start_session_button": "Start protected session" + "start_session_button": "Start protected session", + "started": "Protected session has been started." }, "relation_map": { "open_in_new_tab": "Open in new tab", @@ -992,7 +995,9 @@ "fill_entity_changes_button": "Fill entity changes records", "full_sync_triggered": "Full sync triggered", "filling_entity_changes": "Filling entity changes rows...", - "sync_rows_filled_successfully": "Sync rows filled successfully" + "sync_rows_filled_successfully": "Sync rows filled successfully", + "finished-successfully": "Sync finished successfully.", + "failed": "Sync failed: {{message}}" }, "vacuum_database": { "title": "Vacuum Database", @@ -1311,7 +1316,8 @@ "duplicate-subtree": "Duplicate subtree", "export": "Export", "import-into-note": "Import into note", - "apply-bulk-actions": "Apply bulk actions" + "apply-bulk-actions": "Apply bulk actions", + "converted-to-attachments": "{{count}} notes have been converted to attachments." }, "shared_info": { "shared_publicly": "This note is shared publicly on", @@ -1380,7 +1386,9 @@ "hide-archived-notes": "Hide archived notes", "automatically-collapse-notes": "Automatically collapse notes", "automatically-collapse-notes-title": "Notes will be collapsed after period of inactivity to declutter the tree.", - "save-changes": "Save & apply changes" + "save-changes": "Save & apply changes", + "auto-collapsing-notes-after-inactivity": "Auto collapsing notes after inactivity...", + "saved-search-note-refreshed": "Saved search note refreshed." }, "title_bar_buttons": { "window-on-top": "Keep this window on top." @@ -1424,5 +1432,20 @@ }, "app_context": { "please_wait_for_save": "Please wait for a couple of seconds for the save to finish, then you can try again." + }, + "note_create": { + "duplicated": "Note \"{{title}}\" has been duplicated." + }, + "image": { + "copied-to-clipboard": "A reference to the image has been copied to clipboard. This can be pasted in any text note.", + "cannot-copy": "Could not copy the image reference to clipboard." + }, + "clipboard": { + "cut": "Note(s) have been cut into clipboard.", + "copied": "Note(s) have been copied into clipboard." + }, + "entrypoints": { + "note-revision-created": "Note revision has been created.", + "note-executed": "Note executed." } } diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index a668bab9b..8df347a83 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -785,7 +785,9 @@ "print_note": "Imprimare notiță", "re_render_note": "Reinterpretare notiță", "save_revision": "Salvează o nouă revizie", - "search_in_note": "Caută în notiță" + "search_in_note": "Caută în notiță", + "convert_into_attachment_failed": "Nu s-a putut converti notița „{{title}}”.", + "convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament." }, "note_erasure_timeout": { "deleted_notes_erased": "Notițele șterse au fost eliminate permanent.", @@ -903,7 +905,8 @@ }, "protected_session": { "enter_password_instruction": "Afișarea notițelor protejate necesită introducerea parolei:", - "start_session_button": "Deschide sesiunea protejată" + "start_session_button": "Deschide sesiunea protejată", + "started": "Sesiunea protejată este activă." }, "protected_session_password": { "close_label": "Închide", @@ -1134,7 +1137,9 @@ "force_full_sync_button": "Forțează sincronizare completă", "full_sync_triggered": "S-a activat o sincronizare completă", "sync_rows_filled_successfully": "Rândurile de sincronizare s-au completat cu succes", - "title": "Sincronizare" + "title": "Sincronizare", + "failed": "Eroare la sincronizare: {{message}}", + "finished-successfully": "Sincronizarea a avut succes." }, "sync_2": { "config_title": "Configurația sincronizării", @@ -1281,7 +1286,8 @@ "sort-by": "Ordonare după...", "unprotect-subtree": "Deprotejează ierarhia", "hoist-note": "Focalizează notița", - "unhoist-note": "Defocalizează notița" + "unhoist-note": "Defocalizează notița", + "converted-to-attachments": "{{count}} notițe au fost convertite în atașamente." }, "shared_info": { "help_link": "Pentru informații vizitați wiki-ul.", @@ -1370,7 +1376,9 @@ "hide-archived-notes": "Ascunde notițele arhivate", "save-changes": "Salvează și aplică modificările", "scroll-active-title": "Mergi la notița activă", - "tree-settings-title": "Setări ale ierarhiei notițelor" + "tree-settings-title": "Setări ale ierarhiei notițelor", + "auto-collapsing-notes-after-inactivity": "Se minimizează notițele după inactivitate...", + "saved-search-note-refreshed": "Notița de căutare salvată a fost reîmprospătată." }, "title_bar_buttons": { "window-on-top": "Menține fereastra mereu vizibilă" @@ -1424,5 +1432,20 @@ "file_last_modified": "Fișierul a fost ultima oară modificat la data de .", "ignore_this_change": "Ignoră această schimbare", "upload_modified_file": "Încarcă fișier modificat" + }, + "clipboard": { + "copied": "Notițele au fost copiate în clipboard.", + "cut": "Notițele au fost decupate în clipboard." + }, + "entrypoints": { + "note-executed": "Notița a fost executată.", + "note-revision-created": "S-a creat o revizie a notiței." + }, + "image": { + "cannot-copy": "Nu s-a putut copia în clipboard referința către imagine.", + "copied-to-clipboard": "S-a copiat o referință către imagine în clipboard. Aceasta se poate lipi în orice notiță text." + }, + "note_create": { + "duplicated": "Notița „{{title}}” a fost dublificată." } } From 26e4decaece24a6916c37fef68dbd320d2ef4557 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 02:19:47 +0300 Subject: [PATCH 09/60] i18n: Translate toast errors --- src/public/app/components/entrypoints.js | 2 +- src/public/app/services/branches.js | 6 +++--- .../app/services/frontend_script_api.js | 6 ++---- src/public/app/services/import.js | 2 +- src/public/app/services/protected_session.js | 2 +- src/public/app/services/ws.js | 6 +++--- src/public/translations/en/translation.json | 21 ++++++++++++++++--- src/public/translations/ro/translation.json | 21 ++++++++++++++++--- 8 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/public/app/components/entrypoints.js b/src/public/app/components/entrypoints.js index 3d6c2c2f1..c5fd96e28 100644 --- a/src/public/app/components/entrypoints.js +++ b/src/public/app/components/entrypoints.js @@ -172,7 +172,7 @@ export default class Entrypoints extends Component { const resp = await server.post(`sql/execute/${note.noteId}`); if (!resp.success) { - toastService.showError(`Error occurred while executing SQL query: ${resp.error}`); + toastService.showError(t("entrypoints.sql-error", { message: resp.error })); } await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results}); diff --git a/src/public/app/services/branches.js b/src/public/app/services/branches.js index 64281d361..1f70556ed 100644 --- a/src/public/app/services/branches.js +++ b/src/public/app/services/branches.js @@ -13,7 +13,7 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) { const beforeBranch = froca.getBranch(beforeBranchId); if (['root', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(beforeBranch.noteId)) { - toastService.showError('Cannot move notes here.'); + toastService.showError(t("branches.cannot-move-notes-here")); return; } @@ -42,7 +42,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) { ]; if (forbiddenNoteIds.includes(afterNote.noteId)) { - toastService.showError('Cannot move notes here.'); + toastService.showError(t("branches.cannot-move-notes-here")); return; } @@ -62,7 +62,7 @@ async function moveToParentNote(branchIdsToMove, newParentBranchId) { const newParentBranch = froca.getBranch(newParentBranchId); if (newParentBranch.noteId === '_lbRoot') { - toastService.showError('Cannot move notes here.'); + toastService.showError(t("branches.cannot-move-notes-here")); return; } diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index 1cc79e10f..5af37876a 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -217,8 +217,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain */ this.runOnBackend = async (func, params = []) => { if (func?.constructor.name === "AsyncFunction" || func?.startsWith?.("async ")) { - toastService.showError("You're passing an async function to api.runOnBackend() which will likely not work as you intended. " - + "Either make the function synchronous (by removing 'async' keyword), or use api.runAsyncOnBackendWithManualTransactionHandling()"); + toastService.showError(t("frontend_script_api.async_warning")); } return await this.__runOnBackendInner(func, params, true); @@ -240,8 +239,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain */ this.runAsyncOnBackendWithManualTransactionHandling = async (func, params = []) => { if (func?.constructor.name === "Function" || func?.startsWith?.("function")) { - toastService.showError("You're passing a synchronous function to api.runAsyncOnBackendWithManualTransactionHandling(), " + - "while you should likely use api.runOnBackend() instead."); + toastService.showError(t("frontend_script_api.sync_warning")); } return await this.__runOnBackendInner(func, params, false); diff --git a/src/public/app/services/import.js b/src/public/app/services/import.js index 85f197a58..715380ed8 100644 --- a/src/public/app/services/import.js +++ b/src/public/app/services/import.js @@ -36,7 +36,7 @@ export async function uploadFiles(entityType, parentNoteId, files, options) { type: 'POST', timeout: 60 * 60 * 1000, error: function (xhr) { - toastService.showError(`Import failed: ${xhr.responseText}`); + toastService.showError(t("import.failed", { message: xhr.responseText })); }, contentType: false, // NEEDED, DON'T REMOVE THIS processData: false, // NEEDED, DON'T REMOVE THIS diff --git a/src/public/app/services/protected_session.js b/src/public/app/services/protected_session.js index 24422638d..cf52639b4 100644 --- a/src/public/app/services/protected_session.js +++ b/src/public/app/services/protected_session.js @@ -50,7 +50,7 @@ async function setupProtectedSession(password) { const response = await server.post('login/protected', { password: password }); if (!response.success) { - toastService.showError("Wrong password.", 3000); + toastService.showError(t("protected_session.wrong_password"), 3000); return; } diff --git a/src/public/app/services/ws.js b/src/public/app/services/ws.js index a5fbe60a3..de79632ca 100644 --- a/src/public/app/services/ws.js +++ b/src/public/app/services/ws.js @@ -114,10 +114,10 @@ async function handleMessage(event) { await executeFrontendUpdate(message.data.entityChanges); } else if (message.type === 'sync-hash-check-failed') { - toastService.showError("Sync check failed!", 60000); + toastService.showError(t("ws.sync-check-failed"), 60000); } else if (message.type === 'consistency-checks-failed') { - toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000); + toastService.showError(t("ws.consistency-checks-failed"), 50 * 60000); } else if (message.type === 'api-log-messages') { appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages}); @@ -189,7 +189,7 @@ async function consumeFrontendUpdateData() { else { console.log("nonProcessedEntityChanges causing the timeout", nonProcessedEntityChanges); - toastService.showError(`Encountered error "${e.message}", check out the console.`); + toastService.showError(t("ws.encountered-error", { message: e.message })); } } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index fe5990dc5..8f35f177b 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -166,7 +166,8 @@ "textImportedAsText": "Import HTML, Markdown and TXT as text notes if it's unclear from metadata", "codeImportedAsCode": "Import recognized code files (e.g. .json) as code notes if it's unclear from metadata", "replaceUnderscoresWithSpaces": "Replace underscores with spaces in imported note names", - "import": "Import" + "import": "Import", + "failed": "Import failed: {{message}}." }, "include_note": { "dialog_title": "Include note", @@ -924,7 +925,8 @@ "protected_session": { "enter_password_instruction": "Showing protected note requires entering your password:", "start_session_button": "Start protected session", - "started": "Protected session has been started." + "started": "Protected session has been started.", + "wrong_password": "Wrong password." }, "relation_map": { "open_in_new_tab": "Open in new tab", @@ -1446,6 +1448,19 @@ }, "entrypoints": { "note-revision-created": "Note revision has been created.", - "note-executed": "Note executed." + "note-executed": "Note executed.", + "sql-error": "Error occurred while executing SQL query: {{message}}" + }, + "branches": { + "cannot-move-notes-here": "Cannot move notes here." + }, + "frontend_script_api": { + "async_warning": "You're passing an async function to `api.runOnBackend()` which will likely not work as you intended.\\nEither make the function synchronous (by removing `async` keyword), or use `api.runAsyncOnBackendWithManualTransactionHandling()`.", + "sync_warning": "You're passing a synchronous function to `api.runAsyncOnBackendWithManualTransactionHandling()`,\\nwhile you should likely use `api.runOnBackend()` instead." + }, + "ws": { + "sync-check-failed": "Sync check failed!", + "consistency-checks-failed": "Consistency checks failed! See logs for details.", + "encountered-error": "Encountered error \"{{message}}\", check out the console." } } diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 8df347a83..de59c8c50 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -685,7 +685,8 @@ "safeImportTooltip": "Fișierele de Trilium exportate în format .zip pot conține scripturi executabile ce pot avea un comportament malițios. Importarea sigură va dezactiva execuția automată a tuturor scripturilor importate. Debifați „Importare sigură” dacă arhiva importată conține scripturi executabile dorite și aveți încredere deplină în conținutul acestora.", "shrinkImages": "Micșorare imagini", "shrinkImagesTooltip": "

Dacă bifați această opțiune, Trilium va încerca să micșoreze imaginea importată prin scalarea și importarea ei, aspect ce poate afecta calitatea aparentă a imaginii. Dacă nu este bifat, imaginile vor fi importate fără nicio modificare.

Acest lucru nu se aplică la importuri de tip .zip cu metainformații deoarece se asumă că aceste fișiere sunt deja optimizate.

", - "textImportedAsText": "Importă HTML, Markdown și TXT ca notițe de tip text dacă este neclar din metainformații" + "textImportedAsText": "Importă HTML, Markdown și TXT ca notițe de tip text dacă este neclar din metainformații", + "failed": "Eroare la importare: {{message}}." }, "include_archived_notes": { "include_archived_notes": "Include notițele arhivate" @@ -906,7 +907,8 @@ "protected_session": { "enter_password_instruction": "Afișarea notițelor protejate necesită introducerea parolei:", "start_session_button": "Deschide sesiunea protejată", - "started": "Sesiunea protejată este activă." + "started": "Sesiunea protejată este activă.", + "wrong_password": "Parolă greșită." }, "protected_session_password": { "close_label": "Închide", @@ -1439,7 +1441,8 @@ }, "entrypoints": { "note-executed": "Notița a fost executată.", - "note-revision-created": "S-a creat o revizie a notiței." + "note-revision-created": "S-a creat o revizie a notiței.", + "sql-error": "A apărut o eroare la executarea interogării SQL: {{message}}" }, "image": { "cannot-copy": "Nu s-a putut copia în clipboard referința către imagine.", @@ -1447,5 +1450,17 @@ }, "note_create": { "duplicated": "Notița „{{title}}” a fost dublificată." + }, + "branches": { + "cannot-move-notes-here": "Nu se pot muta notițe aici." + }, + "frontend_script_api": { + "async_warning": "Ați trimis o funcție asincronă metodei `api.runOnBackend()` și este posibil să nu se comporte așa cum vă așteptați.\\nFie faceți metoda sincronă (prin ștergerea cuvântului-cheie `async`), sau folosiți `api.runAsyncOnBackendWithManualTransactionHandling()`.", + "sync_warning": "Ați trimis o funcție sincronă funcției `api.runAsyncOnBackendWithManualTransactionHandling()`,\\ndar cel mai probabil trebuie folosit `api.runOnBackend()` în schimb." + }, + "ws": { + "consistency-checks-failed": "Au fost identificate erori de consistență! Vedeți mai multe detalii în loguri.", + "encountered-error": "A fost întâmpinată o eroare: „{{message}}”. Vedeți în loguri pentru mai multe detalii.", + "sync-check-failed": "Verificările de sincronizare au eșuat!" } } From ae593ea363801254100326368f6d8ea63ed3d26d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 02:34:33 +0300 Subject: [PATCH 10/60] i18n: Translate protected session --- src/public/app/services/protected_session.js | 17 +++++++++++------ src/public/translations/en/translation.json | 8 +++++++- src/public/translations/ro/translation.json | 8 +++++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/public/app/services/protected_session.js b/src/public/app/services/protected_session.js index cf52639b4..e221e0d20 100644 --- a/src/public/app/services/protected_session.js +++ b/src/public/app/services/protected_session.js @@ -6,6 +6,7 @@ import appContext from "../components/app_context.js"; import froca from "./froca.js"; import utils from "./utils.js"; import options from "./options.js"; +import { t } from './i18n.js'; let protectedSessionDeferred = null; @@ -85,10 +86,10 @@ async function protectNote(noteId, protect, includingSubtree) { await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`); } -function makeToast(message, protectingLabel, text) { +function makeToast(message, title, text) { return { id: message.taskId, - title: `${protectingLabel} status`, + title, message: text, icon: message.data.protect ? "check-shield" : "shield" }; @@ -99,15 +100,19 @@ ws.subscribeToMessages(async message => { return; } - const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting"; - + const isProtecting = message.data.protect; + const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title"); + if (message.type === 'taskError') { toastService.closePersistent(message.taskId); toastService.showError(message.message); } else if (message.type === 'taskProgressCount') { - toastService.showPersistent(makeToast(message, protectingLabel,`${protectingLabel} in progress: ${message.progressCount}`)); + const count = message.progressCount; + const text = ( isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count })); + toastService.showPersistent(makeToast(message, title, text)); } else if (message.type === 'taskSucceeded') { - const toast = makeToast(message, protectingLabel, `${protectingLabel} finished successfully.`); + const text = (isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully")) + const toast = makeToast(message, title, text); toast.closeAfter = 3000; toastService.showPersistent(toast); diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 8f35f177b..b5d4de386 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -926,7 +926,13 @@ "enter_password_instruction": "Showing protected note requires entering your password:", "start_session_button": "Start protected session", "started": "Protected session has been started.", - "wrong_password": "Wrong password." + "wrong_password": "Wrong password.", + "protecting-finished-successfully": "Protecting finished successfully.", + "unprotecting-finished-successfully": "Unprotecting finished successfully.", + "protecting-in-progress": "Protecting in progress: {{count}}", + "unprotecting-in-progress-count": "Unprotecting in progress: {{count}}", + "protecting-title": "Protecting status", + "unprotecting-title": "Unprotecting status" }, "relation_map": { "open_in_new_tab": "Open in new tab", diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index de59c8c50..902215f08 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -908,7 +908,13 @@ "enter_password_instruction": "Afișarea notițelor protejate necesită introducerea parolei:", "start_session_button": "Deschide sesiunea protejată", "started": "Sesiunea protejată este activă.", - "wrong_password": "Parolă greșită." + "wrong_password": "Parolă greșită.", + "protecting-finished-successfully": "Protejarea a avut succes.", + "protecting-in-progress": "Protejare în curs: {{count}}", + "protecting-title": "Stare protejare", + "unprotecting-title": "Stare deprotejare", + "unprotecting-finished-successfully": "Deprotejarea a avut succes.", + "unprotecting-in-progress-count": "Deprotejare în curs: {{count}}" }, "protected_session_password": { "close_label": "Închide", From 8cc487da7c8ccc642d486708d7f3809540cdc8b4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 10:49:50 +0300 Subject: [PATCH 11/60] i18n: Translate confirmation popups --- src/public/app/menus/launcher_context_menu.js | 5 ++--- src/public/app/menus/tree_context_menu.js | 2 +- src/public/app/services/hoisted_note.js | 3 ++- src/public/app/widgets/buttons/note_actions.js | 2 +- src/public/app/widgets/note_type.js | 2 +- src/public/translations/en/translation.json | 15 ++++++++++++--- src/public/translations/ro/translation.json | 15 ++++++++++++--- 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/public/app/menus/launcher_context_menu.js b/src/public/app/menus/launcher_context_menu.js index 5f6607f89..1db6bb1f6 100644 --- a/src/public/app/menus/launcher_context_menu.js +++ b/src/public/app/menus/launcher_context_menu.js @@ -3,6 +3,7 @@ import froca from "../services/froca.js"; import contextMenu from "./context_menu.js"; import dialogService from "../services/dialog.js"; import server from "../services/server.js"; +import { t } from '../services/i18n.js'; export default class LauncherContextMenu { /** @@ -53,9 +54,7 @@ export default class LauncherContextMenu { async selectMenuItemHandler({command}) { if (command === 'resetLauncher') { - const confirmed = await dialogService.confirm(`Do you really want to reset "${this.node.title}"? - All data / settings in this note (and its children) will be lost - and the launcher will be returned to its original location.`); + const confirmed = await dialogService.confirm(t("launcher_context_menu.reset_launcher_confirm", { title: this.node.title })); if (confirmed) { await server.post(`special-notes/launchers/${this.node.data.noteId}/reset`); diff --git a/src/public/app/menus/tree_context_menu.js b/src/public/app/menus/tree_context_menu.js index fbac3321c..7609c99f5 100644 --- a/src/public/app/menus/tree_context_menu.js +++ b/src/public/app/menus/tree_context_menu.js @@ -136,7 +136,7 @@ export default class TreeContextMenu { this.treeWidget.triggerCommand("openNewNoteSplit", {ntxId, notePath}); } else if (command === 'convertNoteToAttachment') { - if (!await dialogService.confirm(`Are you sure you want to convert note selected notes into attachments of their parent notes?`)) { + if (!await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm"))) { return; } diff --git a/src/public/app/services/hoisted_note.js b/src/public/app/services/hoisted_note.js index d6a3916de..23d705db8 100644 --- a/src/public/app/services/hoisted_note.js +++ b/src/public/app/services/hoisted_note.js @@ -2,6 +2,7 @@ import appContext from "../components/app_context.js"; import treeService from "./tree.js"; import dialogService from "./dialog.js"; import froca from "./froca.js"; +import { t } from "./i18n.js"; function getHoistedNoteId() { const activeNoteContext = appContext.tabManager.getActiveContext(); @@ -53,7 +54,7 @@ async function checkNoteAccess(notePath, noteContext) { const hoistedNote = await froca.getNote(hoistedNoteId); if ((!hoistedNote.hasAncestor('_hidden') || resolvedNotePath.includes('_lbBookmarks')) - && !await dialogService.confirm(`Requested note '${requestedNote.title}' is outside of hoisted note '${hoistedNote.title}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?`)) { + && !await dialogService.confirm(t("hoisted_note.confirm_unhoisting", { requestedNote: requestedNote.title, hoistedNote: hoistedNote.title }))) { return false; } diff --git a/src/public/app/widgets/buttons/note_actions.js b/src/public/app/widgets/buttons/note_actions.js index dbe324322..bf24a6705 100644 --- a/src/public/app/widgets/buttons/note_actions.js +++ b/src/public/app/widgets/buttons/note_actions.js @@ -127,7 +127,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { } async convertNoteIntoAttachmentCommand() { - if (!await dialogService.confirm(`Are you sure you want to convert note '${this.note.title}' into an attachment of the parent note?`)) { + if (!await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title }))) { return; } diff --git a/src/public/app/widgets/note_type.js b/src/public/app/widgets/note_type.js index 7e15c4f08..7c5003d5b 100644 --- a/src/public/app/widgets/note_type.js +++ b/src/public/app/widgets/note_type.js @@ -159,7 +159,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { return true; } - return await dialogService.confirm("It is not recommended to change note type when note content is not empty. Do you want to continue anyway?"); + return await dialogService.confirm(t("note_types.confirm-change")); } async entitiesReloadedEvent({ loadResults }) { diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index b5d4de386..608f1a0e7 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -635,7 +635,8 @@ "print_note": "Print note", "save_revision": "Save revision", "convert_into_attachment_failed": "Converting note '{{title}}' failed.", - "convert_into_attachment_successful": "Note '{{title}' has been converted to attachment." + "convert_into_attachment_successful": "Note '{{title}' has been converted to attachment.", + "convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?" }, "onclick_button": { "no_click_handler": "Button widget '{{componentId}}' has no defined click handler" @@ -1325,7 +1326,8 @@ "export": "Export", "import-into-note": "Import into note", "apply-bulk-actions": "Apply bulk actions", - "converted-to-attachments": "{{count}} notes have been converted to attachments." + "converted-to-attachments": "{{count}} notes have been converted to attachments.", + "convert-to-attachment-confirm": "Are you sure you want to convert note selected notes into attachments of their parent notes?" }, "shared_info": { "shared_publicly": "This note is shared publicly on", @@ -1348,7 +1350,8 @@ "image": "Image", "launcher": "Launcher", "doc": "Doc", - "widget": "Widget" + "widget": "Widget", + "confirm-change": "It is not recommended to change note type when note content is not empty. Do you want to continue anyway?" }, "protect_note": { "toggle-on": "Protect the note", @@ -1468,5 +1471,11 @@ "sync-check-failed": "Sync check failed!", "consistency-checks-failed": "Consistency checks failed! See logs for details.", "encountered-error": "Encountered error \"{{message}}\", check out the console." + }, + "hoisted_note": { + "confirm_unhoisting": "Requested note '{{requestedNote}}' is outside of hoisted note '{{hoistedNote}}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?" + }, + "launcher_context_menu": { + "reset_launcher_confirm": "Do you really want to reset \"{{title}}\"? All data / settings in this note (and its children) will be lost and the launcher will be returned to its original location." } } diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 902215f08..fb6521a32 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -788,7 +788,8 @@ "save_revision": "Salvează o nouă revizie", "search_in_note": "Caută în notiță", "convert_into_attachment_failed": "Nu s-a putut converti notița „{{title}}”.", - "convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament." + "convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament.", + "convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?" }, "note_erasure_timeout": { "deleted_notes_erased": "Notițele șterse au fost eliminate permanent.", @@ -1295,7 +1296,8 @@ "unprotect-subtree": "Deprotejează ierarhia", "hoist-note": "Focalizează notița", "unhoist-note": "Defocalizează notița", - "converted-to-attachments": "{{count}} notițe au fost convertite în atașamente." + "converted-to-attachments": "{{count}} notițe au fost convertite în atașamente.", + "convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte?" }, "shared_info": { "help_link": "Pentru informații vizitați wiki-ul.", @@ -1318,7 +1320,8 @@ "file": "Fișier", "image": "Imagine", "launcher": "Scurtătură", - "widget": "Widget" + "widget": "Widget", + "confirm-change": "Nu se recomandă schimbarea tipului notiței atunci când ea are un conținut. Procedați oricum?" }, "protect_note": { "toggle-off": "Deprotejează notița", @@ -1468,5 +1471,11 @@ "consistency-checks-failed": "Au fost identificate erori de consistență! Vedeți mai multe detalii în loguri.", "encountered-error": "A fost întâmpinată o eroare: „{{message}}”. Vedeți în loguri pentru mai multe detalii.", "sync-check-failed": "Verificările de sincronizare au eșuat!" + }, + "hoisted_note": { + "confirm_unhoisting": "Notița dorită „{{requestedNote}}” este în afara ierarhiei notiței focalizate „{{hoistedNote}}”. Doriți defocalizarea pentru a accesa notița?" + }, + "launcher_context_menu": { + "reset_launcher_confirm": "Doriți resetarea lansatorului „{{title}}”? Toate datele și setările din această notiță (și subnotițele ei) vor fi pierdute, iar lansatorul va fi resetat în poziția lui originală." } } From 9ed7eb977ebf598721204ab392e648872537d569 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 10:56:56 +0300 Subject: [PATCH 12/60] i18n: Translate launcher context menu --- src/public/app/menus/launcher_context_menu.js | 20 +++++++++---------- src/public/translations/en/translation.json | 11 +++++++++- src/public/translations/ro/translation.json | 11 +++++++++- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/public/app/menus/launcher_context_menu.js b/src/public/app/menus/launcher_context_menu.js index 1db6bb1f6..b7c722dae 100644 --- a/src/public/app/menus/launcher_context_menu.js +++ b/src/public/app/menus/launcher_context_menu.js @@ -34,20 +34,20 @@ export default class LauncherContextMenu { const isAvailableItem = parentNoteId === '_lbAvailableLaunchers'; const isItem = isVisibleItem || isAvailableItem; const canBeDeleted = !note.noteId.startsWith("_"); // fixed notes can't be deleted - const canBeReset = !canBeDeleted && note.isLaunchBarConfig();; + const canBeReset = !canBeDeleted && note.isLaunchBarConfig(); return [ - (isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add a script launcher', command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add a custom widget', command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add spacer', command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-note-launcher"), command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-script-launcher"), command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-custom-widget"), command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-spacer"), command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null, (isVisibleRoot || isAvailableRoot) ? { title: "----" } : null, - { title: 'Delete ', command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted }, - { title: 'Reset', command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset}, + { title: `${t("launcher_context_menu.delete")} `, command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted }, + { title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset}, { title: "----" }, - isAvailableItem ? { title: 'Move to visible launchers', command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null, - isVisibleItem ? { title: 'Move to available launchers', command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null, - { title: `Duplicate launcher `, command: "duplicateSubtree", uiIcon: "bx bx-empty", + isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null, + isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null, + { title: `${t("launcher_context_menu.duplicate-launcher")} `, command: "duplicateSubtree", uiIcon: "bx bx-empty", enabled: isItem } ].filter(row => row !== null); } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 608f1a0e7..2ca89c155 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1476,6 +1476,15 @@ "confirm_unhoisting": "Requested note '{{requestedNote}}' is outside of hoisted note '{{hoistedNote}}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?" }, "launcher_context_menu": { - "reset_launcher_confirm": "Do you really want to reset \"{{title}}\"? All data / settings in this note (and its children) will be lost and the launcher will be returned to its original location." + "reset_launcher_confirm": "Do you really want to reset \"{{title}}\"? All data / settings in this note (and its children) will be lost and the launcher will be returned to its original location.", + "add-note-launcher": "Add a note launcher", + "add-script-launcher": "Add a script launcher", + "add-custom-widget": "Add a custom widget", + "add-spacer": "Add spacer", + "delete": "Delete", + "reset": "Reset", + "move-to-visible-launchers": "Move to visible launchers", + "move-to-available-launchers": "Move to available launchers", + "duplicate-launcher": "Duplicate launcher" } } diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index fb6521a32..dd4052b80 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1476,6 +1476,15 @@ "confirm_unhoisting": "Notița dorită „{{requestedNote}}” este în afara ierarhiei notiței focalizate „{{hoistedNote}}”. Doriți defocalizarea pentru a accesa notița?" }, "launcher_context_menu": { - "reset_launcher_confirm": "Doriți resetarea lansatorului „{{title}}”? Toate datele și setările din această notiță (și subnotițele ei) vor fi pierdute, iar lansatorul va fi resetat în poziția lui originală." + "reset_launcher_confirm": "Doriți resetarea lansatorului „{{title}}”? Toate datele și setările din această notiță (și subnotițele ei) vor fi pierdute, iar lansatorul va fi resetat în poziția lui originală.", + "add-custom-widget": "Adaugă un widget personalizat", + "add-note-launcher": "Adaugă un lansator de notiță", + "add-script-launcher": "Adaugă un lansator de script", + "add-spacer": "Adaugă un separator", + "delete": "Șterge", + "duplicate-launcher": "Dublifică lansatorul", + "move-to-available-launchers": "Mută în Lansatoare disponibile", + "move-to-visible-launchers": "Mută în Lansatoare vizibile", + "reset": "Resetează" } } From b5ee90a1d2664bf7f36f3f28b16918336822dae3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 11:45:53 +0300 Subject: [PATCH 13/60] i18n: Translate delete/restore branch --- src/public/app/services/branches.js | 11 ++++++----- src/public/translations/en/translation.json | 7 ++++++- src/public/translations/ro/translation.json | 7 ++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/public/app/services/branches.js b/src/public/app/services/branches.js index 1f70556ed..803c59147 100644 --- a/src/public/app/services/branches.js +++ b/src/public/app/services/branches.js @@ -5,6 +5,7 @@ import froca from "./froca.js"; import hoistedNoteService from "./hoisted_note.js"; import ws from "./ws.js"; import appContext from "../components/app_context.js"; +import { t } from './i18n.js'; async function moveBeforeBranch(branchIdsToMove, beforeBranchId) { branchIdsToMove = filterRootNote(branchIdsToMove); @@ -192,7 +193,7 @@ function filterRootNote(branchIds) { function makeToast(id, message) { return { id: id, - title: "Delete status", + title: t("branches.delete-status"), message: message, icon: "trash" }; @@ -207,9 +208,9 @@ ws.subscribeToMessages(async message => { toastService.closePersistent(message.taskId); toastService.showError(message.message); } else if (message.type === 'taskProgressCount') { - toastService.showPersistent(makeToast(message.taskId, `Delete notes in progress: ${message.progressCount}`)); + toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount }))); } else if (message.type === 'taskSucceeded') { - const toast = makeToast(message.taskId, "Delete finished successfully."); + const toast = makeToast(message.taskId, t("branches.delete-finished-successfully")); toast.closeAfter = 5000; toastService.showPersistent(toast); @@ -225,9 +226,9 @@ ws.subscribeToMessages(async message => { toastService.closePersistent(message.taskId); toastService.showError(message.message); } else if (message.type === 'taskProgressCount') { - toastService.showPersistent(makeToast(message.taskId, `Undeleting notes in progress: ${message.progressCount}`)); + toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount }))); } else if (message.type === 'taskSucceeded') { - const toast = makeToast(message.taskId, "Undeleting notes finished successfully."); + const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully")); toast.closeAfter = 5000; toastService.showPersistent(toast); diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 2ca89c155..35fb226e5 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1461,7 +1461,12 @@ "sql-error": "Error occurred while executing SQL query: {{message}}" }, "branches": { - "cannot-move-notes-here": "Cannot move notes here." + "cannot-move-notes-here": "Cannot move notes here.", + "delete-status": "Delete status", + "delete-notes-in-progress": "Delete notes in progress: {{count}}", + "delete-finished-successfully": "Delete finished successfully.", + "undeleting-notes-in-progress": "Undeleting notes in progress: {{count}}", + "undeleting-notes-finished-successfully": "Undeleting notes finished successfully." }, "frontend_script_api": { "async_warning": "You're passing an async function to `api.runOnBackend()` which will likely not work as you intended.\\nEither make the function synchronous (by removing `async` keyword), or use `api.runAsyncOnBackendWithManualTransactionHandling()`.", diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index dd4052b80..a6bed6d62 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1461,7 +1461,12 @@ "duplicated": "Notița „{{title}}” a fost dublificată." }, "branches": { - "cannot-move-notes-here": "Nu se pot muta notițe aici." + "cannot-move-notes-here": "Nu se pot muta notițe aici.", + "delete-finished-successfully": "Ștergerea a avut succes.", + "delete-notes-in-progress": "Ștergere în curs: {{count}}", + "delete-status": "Starea ștergerii", + "undeleting-notes-finished-successfully": "Restaurarea notițelor a avut succes.", + "undeleting-notes-in-progress": "Restaurare notițe în curs: {{count}}" }, "frontend_script_api": { "async_warning": "Ați trimis o funcție asincronă metodei `api.runOnBackend()` și este posibil să nu se comporte așa cum vă așteptați.\\nFie faceți metoda sincronă (prin ștergerea cuvântului-cheie `async`), sau folosiți `api.runAsyncOnBackendWithManualTransactionHandling()`.", From cb9403535da3d05bb37798f4ce91f9a7809ad445 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 12:15:02 +0300 Subject: [PATCH 14/60] i18next: 23.16.0 -> 23.16.1 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 134ccbea1..3d94906f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "html2plaintext": "2.1.4", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.5", - "i18next": "23.16.0", + "i18next": "23.16.1", "i18next-fs-backend": "2.3.2", "i18next-http-backend": "2.6.2", "image-type": "4.1.0", @@ -9715,9 +9715,9 @@ } }, "node_modules/i18next": { - "version": "23.16.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.0.tgz", - "integrity": "sha512-Ni3CG6c14teOogY19YNRl+kYaE/Rb59khy0VyHVn4uOZ97E2E/Yziyi6r3C3s9+wacjdLZiq/LLYyx+Cgd+FCw==", + "version": "23.16.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.1.tgz", + "integrity": "sha512-H73h/H7BN7PI38Sq9XsOXzWFBH6mtyCYFiUMVtd9BxiYNDWPPIzKcBmDrqhjKbw3IXP5j6JoSW4ugJlaZuOvKw==", "funding": [ { "type": "individual", diff --git a/package.json b/package.json index b8c4a4132..4ae7dcaa5 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "html2plaintext": "2.1.4", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.5", - "i18next": "23.16.0", + "i18next": "23.16.1", "i18next-fs-backend": "2.3.2", "i18next-http-backend": "2.6.2", "image-type": "4.1.0", From 31ccbb0d23418dd1515a0ff6079310c736a84e46 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 20 Oct 2024 12:19:41 +0300 Subject: [PATCH 15/60] mind-elixir: 4.2.0 -> 4.2.2 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d94906f9..b8a1e3a6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "marked": "14.1.2", "mermaid": "11.3.0", "mime-types": "2.1.35", - "mind-elixir": "4.2.0", + "mind-elixir": "4.2.2", "multer": "1.4.5-lts.1", "node-abi": "3.67.0", "normalize-strings": "1.1.1", @@ -12028,9 +12028,9 @@ } }, "node_modules/mind-elixir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.2.0.tgz", - "integrity": "sha512-FBmTri+lfuScRtaBQGh6WNFU1Bbqp2IDq6dF9coxv5aj34wuTsacR8fwyTaK79Wh8A5mUL/D/HO/KvSMBKJBqg==" + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.2.2.tgz", + "integrity": "sha512-Dfb3fb0IAVpAhOh0c9zmHgtLQI++ODbFawNbHk2xkyZ5uLuZPkkN1a5rtrUkqYFxglxflpmsPElTY0zqxxlPrA==" }, "node_modules/minimalistic-assert": { "version": "1.0.1", diff --git a/package.json b/package.json index 4ae7dcaa5..4f7de8237 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "marked": "14.1.2", "mermaid": "11.3.0", "mime-types": "2.1.35", - "mind-elixir": "4.2.0", + "mind-elixir": "4.2.2", "multer": "1.4.5-lts.1", "node-abi": "3.67.0", "normalize-strings": "1.1.1", From b96047e9628823f781f3a0ac7cbbc6c71676d87a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 19:22:14 +0300 Subject: [PATCH 16/60] vanilla-js-wheel-zoom: 9.0.2 -> 9.0.4 --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8a1e3a6c..683b73017 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,7 @@ "tree-kill": "1.2.2", "turndown": "7.2.0", "unescape": "1.0.1", - "vanilla-js-wheel-zoom": "9.0.2", + "vanilla-js-wheel-zoom": "9.0.4", "ws": "8.18.0", "xml2js": "0.6.2", "yauzl": "3.1.3" @@ -16032,10 +16032,9 @@ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "node_modules/vanilla-js-wheel-zoom": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/vanilla-js-wheel-zoom/-/vanilla-js-wheel-zoom-9.0.2.tgz", - "integrity": "sha512-GleJm/qTDcfQC7gdFH4BT5vCZmnz2LOTgLAQ5CCCwftFC/zrBURRWr5Y7jRPi3W3rRf8bTIAN3hLZYFqvK5jwA==", - "license": "MIT" + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/vanilla-js-wheel-zoom/-/vanilla-js-wheel-zoom-9.0.4.tgz", + "integrity": "sha512-OjmS9ihEKBCRw2OQ7IiIdQGXdC5gTEEmtcAWZcPeNAJaYiS61KCd02Z72YMtIoXLGN5TZP+wliBMylLAsr6wow==" }, "node_modules/vary": { "version": "1.1.2", diff --git a/package.json b/package.json index 4f7de8237..108ae8c84 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "tree-kill": "1.2.2", "turndown": "7.2.0", "unescape": "1.0.1", - "vanilla-js-wheel-zoom": "9.0.2", + "vanilla-js-wheel-zoom": "9.0.4", "ws": "8.18.0", "xml2js": "0.6.2", "yauzl": "3.1.3" From b5bc93d79467f0d623adea3bf4949ebd49e6c25f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 19:24:16 +0300 Subject: [PATCH 17/60] i18next: 23.16.1 -> 23.16.2 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 683b73017..8d537f451 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "html2plaintext": "2.1.4", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.5", - "i18next": "23.16.1", + "i18next": "23.16.2", "i18next-fs-backend": "2.3.2", "i18next-http-backend": "2.6.2", "image-type": "4.1.0", @@ -9715,9 +9715,9 @@ } }, "node_modules/i18next": { - "version": "23.16.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.1.tgz", - "integrity": "sha512-H73h/H7BN7PI38Sq9XsOXzWFBH6mtyCYFiUMVtd9BxiYNDWPPIzKcBmDrqhjKbw3IXP5j6JoSW4ugJlaZuOvKw==", + "version": "23.16.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.2.tgz", + "integrity": "sha512-dFyxwLXxEQK32f6tITBMaRht25mZPJhQ0WbC0p3bO2mWBal9lABTMqSka5k+GLSRWLzeJBKDpH7BeIA9TZI7Jg==", "funding": [ { "type": "individual", diff --git a/package.json b/package.json index 108ae8c84..6b3b6bbd6 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "html2plaintext": "2.1.4", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.5", - "i18next": "23.16.1", + "i18next": "23.16.2", "i18next-fs-backend": "2.3.2", "i18next-http-backend": "2.6.2", "image-type": "4.1.0", From cda369ed4d0d5960b92aefe451861799b13b7ff5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 19:25:47 +0300 Subject: [PATCH 18/60] server: Update express, express-rate-limit, express-session to latest --- package-lock.json | 40 ++++++++++++++++++++-------------------- package.json | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d537f451..927b0c789 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,10 +35,10 @@ "electron-window-state": "5.0.3", "escape-html": "1.0.3", "eslint": "9.10.0", - "express": "4.21.0", + "express": "4.21.1", "express-partial-content": "1.0.2", - "express-rate-limit": "7.4.0", - "express-session": "1.18.0", + "express-rate-limit": "7.4.1", + "express-session": "1.18.1", "force-graph": "1.43.5", "fs-extra": "11.2.0", "helmet": "7.1.0", @@ -8229,16 +8229,16 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -8278,9 +8278,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.0.tgz", - "integrity": "sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz", + "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==", "engines": { "node": ">= 16" }, @@ -8292,11 +8292,11 @@ } }, "node_modules/express-session": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", - "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", "dependencies": { - "cookie": "0.6.0", + "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", @@ -8310,9 +8310,9 @@ } }, "node_modules/express-session/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "engines": { "node": ">= 0.6" } @@ -8358,9 +8358,9 @@ ] }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } diff --git a/package.json b/package.json index 6b3b6bbd6..70d20cd2f 100644 --- a/package.json +++ b/package.json @@ -75,10 +75,10 @@ "electron-window-state": "5.0.3", "escape-html": "1.0.3", "eslint": "9.10.0", - "express": "4.21.0", + "express": "4.21.1", "express-partial-content": "1.0.2", - "express-rate-limit": "7.4.0", - "express-session": "1.18.0", + "express-rate-limit": "7.4.1", + "express-session": "1.18.1", "force-graph": "1.43.5", "fs-extra": "11.2.0", "helmet": "7.1.0", From 8b333b32af1a3eeaa99838e169a34f4c56b2e900 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 19:27:53 +0300 Subject: [PATCH 19/60] mind-elixir: 4.2.2 -> 4.2.3 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 927b0c789..a867cbd62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "marked": "14.1.2", "mermaid": "11.3.0", "mime-types": "2.1.35", - "mind-elixir": "4.2.2", + "mind-elixir": "4.2.3", "multer": "1.4.5-lts.1", "node-abi": "3.67.0", "normalize-strings": "1.1.1", @@ -12028,9 +12028,9 @@ } }, "node_modules/mind-elixir": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.2.2.tgz", - "integrity": "sha512-Dfb3fb0IAVpAhOh0c9zmHgtLQI++ODbFawNbHk2xkyZ5uLuZPkkN1a5rtrUkqYFxglxflpmsPElTY0zqxxlPrA==" + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.2.3.tgz", + "integrity": "sha512-o/t9/mrJkRu0PE5UjXBv8ZZuhwSdm6C1Hw65v/+bIlB2CS2MOGZ/GNPvU3U4kPDu6LnCZ0kw0L7hoVfHhrZLtw==" }, "node_modules/minimalistic-assert": { "version": "1.0.1", diff --git a/package.json b/package.json index 70d20cd2f..21c968225 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "marked": "14.1.2", "mermaid": "11.3.0", "mime-types": "2.1.35", - "mind-elixir": "4.2.2", + "mind-elixir": "4.2.3", "multer": "1.4.5-lts.1", "node-abi": "3.67.0", "normalize-strings": "1.1.1", From e8d151896513ccb0679a1a5170c0a68961b61069 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 19:30:34 +0300 Subject: [PATCH 20/60] build: Update TypeScript --- package-lock.json | 32 ++++++++++++++++---------------- package.json | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index a867cbd62..06760e8d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -118,7 +118,7 @@ "@types/ejs": "3.1.5", "@types/electron-squirrel-startup": "1.0.2", "@types/escape-html": "1.0.4", - "@types/express": "4.17.21", + "@types/express": "5.0.0", "@types/express-session": "1.18.0", "@types/html": "1.0.4", "@types/ini": "4.1.1", @@ -126,7 +126,7 @@ "@types/jsdom": "21.1.7", "@types/mime-types": "2.1.4", "@types/multer": "1.4.12", - "@types/node": "22.5.4", + "@types/node": "22.7.8", "@types/safe-compare": "1.1.2", "@types/sanitize-html": "2.13.0", "@types/sax": "1.2.7", @@ -152,7 +152,7 @@ "rcedit": "4.0.1", "rimraf": "6.0.1", "ts-node": "10.9.2", - "tslib": "2.7.0", + "tslib": "2.8.0", "tsx": "4.19.1", "typescript": "5.6.3", "webpack": "5.95.0", @@ -3348,21 +3348,21 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", "dev": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", + "@types/express-serve-static-core": "^5.0.0", "@types/qs": "*", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", "dev": true, "dependencies": { "@types/node": "*", @@ -3528,9 +3528,9 @@ } }, "node_modules/@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "version": "22.7.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.8.tgz", + "integrity": "sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg==", "dependencies": { "undici-types": "~6.19.2" } @@ -15647,9 +15647,9 @@ } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" }, "node_modules/tsscmp": { "version": "1.0.6", diff --git a/package.json b/package.json index 21c968225..c098be737 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "@types/ejs": "3.1.5", "@types/electron-squirrel-startup": "1.0.2", "@types/escape-html": "1.0.4", - "@types/express": "4.17.21", + "@types/express": "5.0.0", "@types/express-session": "1.18.0", "@types/html": "1.0.4", "@types/ini": "4.1.1", @@ -163,7 +163,7 @@ "@types/jsdom": "21.1.7", "@types/mime-types": "2.1.4", "@types/multer": "1.4.12", - "@types/node": "22.5.4", + "@types/node": "22.7.8", "@types/safe-compare": "1.1.2", "@types/sanitize-html": "2.13.0", "@types/sax": "1.2.7", @@ -189,7 +189,7 @@ "rcedit": "4.0.1", "rimraf": "6.0.1", "ts-node": "10.9.2", - "tslib": "2.7.0", + "tslib": "2.8.0", "tsx": "4.19.1", "typescript": "5.6.3", "webpack": "5.95.0", From a2f0cb394a7b2e0c6a4a01653838be6adb78d90c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 19:53:22 +0300 Subject: [PATCH 21/60] server: Update marked, sanitize-html to latest --- package-lock.json | 32 ++++++++++++++++---------------- package.json | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06760e8d5..d8f47a916 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "cls-hooked": "4.2.2", "codemirror": "5.65.18", "compression": "1.7.4", - "cookie-parser": "1.4.6", + "cookie-parser": "1.4.7", "csurf": "1.11.0", "dayjs": "1.11.13", "dayjs-plugin-utc": "0.1.2", @@ -63,7 +63,7 @@ "katex": "0.16.11", "knockout": "3.5.1", "mark.js": "8.11.1", - "marked": "14.1.2", + "marked": "14.1.3", "mermaid": "11.3.0", "mime-types": "2.1.35", "mind-elixir": "4.2.3", @@ -79,7 +79,7 @@ "request": "2.88.2", "safe-compare": "1.1.4", "sanitize-filename": "1.6.3", - "sanitize-html": "2.13.0", + "sanitize-html": "2.13.1", "sax": "1.4.1", "semver": "7.6.3", "serve-favicon": "2.5.0", @@ -5926,11 +5926,11 @@ } }, "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { @@ -5938,9 +5938,9 @@ } }, "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "engines": { "node": ">= 0.6" } @@ -11817,9 +11817,9 @@ } }, "node_modules/marked": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.2.tgz", - "integrity": "sha512-f3r0yqpz31VXiDB/wj9GaOB0a2PRLQl6vJmXiFrniNwjkKdvakqJRULhjFKJpxOchlCRiG5fcacoUZY5Xa6PEQ==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz", + "integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==", "bin": { "marked": "bin/marked.js" }, @@ -14321,9 +14321,9 @@ } }, "node_modules/sanitize-html": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz", - "integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.1.tgz", + "integrity": "sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", diff --git a/package.json b/package.json index c098be737..3db51f322 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "cls-hooked": "4.2.2", "codemirror": "5.65.18", "compression": "1.7.4", - "cookie-parser": "1.4.6", + "cookie-parser": "1.4.7", "csurf": "1.11.0", "dayjs": "1.11.13", "dayjs-plugin-utc": "0.1.2", @@ -103,7 +103,7 @@ "katex": "0.16.11", "knockout": "3.5.1", "mark.js": "8.11.1", - "marked": "14.1.2", + "marked": "14.1.3", "mermaid": "11.3.0", "mime-types": "2.1.35", "mind-elixir": "4.2.3", @@ -119,7 +119,7 @@ "request": "2.88.2", "safe-compare": "1.1.4", "sanitize-filename": "1.6.3", - "sanitize-html": "2.13.0", + "sanitize-html": "2.13.1", "sax": "1.4.1", "semver": "7.6.3", "serve-favicon": "2.5.0", From 7aafdce629b7447c25cc898b334432ea5b13f643 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 19:56:15 +0300 Subject: [PATCH 22/60] server: Update jasmine, debounce --- package-lock.json | 24 ++++++++++++------------ package.json | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8f47a916..f25c8e1ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "csurf": "1.11.0", "dayjs": "1.11.13", "dayjs-plugin-utc": "0.1.2", - "debounce": "2.1.1", + "debounce": "2.2.0", "ejs": "3.1.10", "electron-debug": "4.0.1", "electron-dl": "4.0.0", @@ -145,7 +145,7 @@ "electron-rebuild": "3.2.9", "esm": "3.2.25", "iconsur": "1.7.0", - "jasmine": "5.3.1", + "jasmine": "5.4.0", "jsdoc": "4.0.3", "lorem-ipsum": "2.0.8", "nodemon": "3.1.7", @@ -6753,9 +6753,9 @@ "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg==" }, "node_modules/debounce": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.1.1.tgz", - "integrity": "sha512-+xRWxgel9LgTC4PwKlm7TJUK6B6qsEK77NaiNvXmeQ7Y3e6OVVsBC4a9BSptS/mAYceyAz37Oa8JTTuPRft7uQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", "engines": { "node": ">=18" }, @@ -10828,22 +10828,22 @@ } }, "node_modules/jasmine": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.3.1.tgz", - "integrity": "sha512-3zeUCfr3d1iga3s+NgDpggCP+ex5sdbNgqNn+Tq4yw/QfnwGrWC/ZvXX1IRm5deSIZ1LnvoeGY55F/ztbVOXPQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.4.0.tgz", + "integrity": "sha512-E2u4ylX5tgGYvbynImU6EUBKKrSVB1L72FEPjGh4M55ov1VsxR26RA2JU91L9YSPFgcjo4mCLyKn/QXvEYGBkA==", "dev": true, "dependencies": { "glob": "^10.2.2", - "jasmine-core": "~5.3.0" + "jasmine-core": "~5.4.0" }, "bin": { "jasmine": "bin/jasmine.js" } }, "node_modules/jasmine-core": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.3.0.tgz", - "integrity": "sha512-zsOmeBKESky4toybvWEikRiZ0jHoBEu79wNArLfMdSnlLMZx3Xcp6CSm2sUcYyoJC+Uyj8LBJap/MUbVSfJ27g==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.4.0.tgz", + "integrity": "sha512-T4fio3W++llLd7LGSGsioriDHgWyhoL6YTu4k37uwJLF7DzOzspz7mNxRoM3cQdLWtL/ebazQpIf/yZGJx/gzg==", "dev": true }, "node_modules/jasmine/node_modules/brace-expansion": { diff --git a/package.json b/package.json index 3db51f322..bccc61857 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "csurf": "1.11.0", "dayjs": "1.11.13", "dayjs-plugin-utc": "0.1.2", - "debounce": "2.1.1", + "debounce": "2.2.0", "ejs": "3.1.10", "electron-debug": "4.0.1", "electron-dl": "4.0.0", @@ -182,7 +182,7 @@ "electron-rebuild": "3.2.9", "esm": "3.2.25", "iconsur": "1.7.0", - "jasmine": "5.3.1", + "jasmine": "5.4.0", "jsdoc": "4.0.3", "lorem-ipsum": "2.0.8", "nodemon": "3.1.7", From e77b2235083cc32969220d70894d3e1b68e5044f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 20:03:05 +0300 Subject: [PATCH 23/60] client: Update force-graph 1.43.5 -> 1.45.0 --- package-lock.json | 18 +++++++++--------- package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index f25c8e1ea..7005c3582 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "express-partial-content": "1.0.2", "express-rate-limit": "7.4.1", "express-session": "1.18.1", - "force-graph": "1.43.5", + "force-graph": "1.45.0", "fs-extra": "11.2.0", "helmet": "7.1.0", "html": "1.0.0", @@ -5319,9 +5319,9 @@ ] }, "node_modules/canvas-color-tracker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.2.1.tgz", - "integrity": "sha512-i5clg2pEdaWqHuEM/B74NZNLkHh5+OkXbA/T4iaBiaNDagkOCXkLNrhqUfdUugsRwuaNRU20e/OygzxWRor3yg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.1.tgz", + "integrity": "sha512-eNycxGS7oQ3IS/9QQY41f/aQjiO9Y/MtedhCgSdsbLSxC9EyUD8L3ehl/Q3Kfmvt8um79S45PBV+5Rxm5ztdSw==", "dependencies": { "tinycolor2": "^1.6.0" }, @@ -8835,14 +8835,14 @@ } }, "node_modules/force-graph": { - "version": "1.43.5", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.43.5.tgz", - "integrity": "sha512-HveLELh9yhZXO/QOfaFS38vlwJZ/3sKu+jarfXzRmbmihSOH/BbRWnUvmg8wLFiYy6h4HlH4lkRfZRccHYmXgA==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.45.0.tgz", + "integrity": "sha512-QM/J72Vji5D3ug+TDu8wH+qne0zEKE9Cn7m9ocH/1RtaVY0BBqZQ4Mn6MiwNRyxwl28lsUd0F54kDpINnagvOA==", "dependencies": { - "@tweenjs/tween.js": "18 - 23", + "@tweenjs/tween.js": "18 - 25", "accessor-fn": "1", "bezier-js": "3 - 6", - "canvas-color-tracker": "1", + "canvas-color-tracker": "^1.3", "d3-array": "1 - 3", "d3-drag": "2 - 3", "d3-force-3d": "2 - 3", diff --git a/package.json b/package.json index bccc61857..f6d2178d1 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "express-partial-content": "1.0.2", "express-rate-limit": "7.4.1", "express-session": "1.18.1", - "force-graph": "1.43.5", + "force-graph": "1.45.0", "fs-extra": "11.2.0", "helmet": "7.1.0", "html": "1.0.0", From 58132822481e2514c41fe453f57ad7005772105b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 20:05:43 +0300 Subject: [PATCH 24/60] Prepare for 0.90.9-beta --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7005c3582..6b2adcb0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.90.8", + "version": "0.90.9-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.90.8", + "version": "0.90.9-beta", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "7.1.0", diff --git a/package.json b/package.json index f6d2178d1..03c2054d2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "TriliumNext Notes", "description": "Build your personal knowledge base with TriliumNext Notes", - "version": "0.90.8", + "version": "0.90.9-beta", "license": "AGPL-3.0-only", "main": "./dist/electron-main.js", "author": { From 77550f30875edeb5c8ee7ecde7d8732e5dfa35d4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 22 Oct 2024 20:09:42 +0300 Subject: [PATCH 25/60] server: Fix regression due to express types --- package-lock.json | 22 +++++++++++++++++----- package.json | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b2adcb0d..aed9eedac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -118,7 +118,7 @@ "@types/ejs": "3.1.5", "@types/electron-squirrel-startup": "1.0.2", "@types/escape-html": "1.0.4", - "@types/express": "5.0.0", + "@types/express": "4.17.21", "@types/express-session": "1.18.0", "@types/html": "1.0.4", "@types/ini": "4.1.1", @@ -3348,13 +3348,13 @@ "dev": true }, "node_modules/@types/express": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", - "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } @@ -3380,6 +3380,18 @@ "@types/express": "*" } }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", diff --git a/package.json b/package.json index 03c2054d2..c0f1e7bdf 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "@types/ejs": "3.1.5", "@types/electron-squirrel-startup": "1.0.2", "@types/escape-html": "1.0.4", - "@types/express": "5.0.0", + "@types/express": "4.17.21", "@types/express-session": "1.18.0", "@types/html": "1.0.4", "@types/ini": "4.1.1", From af67362ad65e699c948020469dd4e2225ad72d22 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 23 Oct 2024 19:34:09 +0300 Subject: [PATCH 26/60] server: Translate weekday and month names --- src/services/date_notes.ts | 14 ++++++++++---- translations/en/server.json | 23 +++++++++++++++++++++++ translations/ro/server.json | 23 +++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/services/date_notes.ts b/src/services/date_notes.ts index 465757328..3f355f429 100644 --- a/src/services/date_notes.ts +++ b/src/services/date_notes.ts @@ -9,14 +9,20 @@ import searchService from "../services/search/services/search.js"; import SearchContext from "../services/search/search_context.js"; import hoistedNoteService from "./hoisted_note.js"; import BNote from "../becca/entities/bnote.js"; +import { t } from "i18next"; const CALENDAR_ROOT_LABEL = 'calendarRoot'; const YEAR_LABEL = 'yearNote'; const MONTH_LABEL = 'monthNote'; const DATE_LABEL = 'dateNote'; -const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; -const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; +const WEEKDAY_TRANSLATION_IDS = [ + "weekdays.sunday", "weekdays.monday", "weekdays.tuesday", "weekdays.wednesday", "weekdays.thursday", "weekdays.friday", "weekdays.saturday", "weekdays.sunday" +]; + +const MONTH_TRANSLATION_IDS = [ + "months.january", "months.february", "months.march", "months.april", "months.may", "months.june", "months.july", "months.august", "months.september", "months.october", "months.november", "months.december" +]; type StartOfWeek = "monday" | "sunday"; @@ -92,7 +98,7 @@ function getYearNote(dateStr: string, _rootNote: BNote | null = null): BNote { function getMonthNoteTitle(rootNote: BNote, monthNumber: string, dateObj: Date) { const pattern = rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}"; - const monthName = MONTHS[dateObj.getMonth()]; + const monthName = t(MONTH_TRANSLATION_IDS[dateObj.getMonth()]); return pattern .replace(/{shortMonth3}/g, monthName.slice(0,3)) @@ -138,7 +144,7 @@ function getMonthNote(dateStr: string, _rootNote: BNote | null = null): BNote { function getDayNoteTitle(rootNote: BNote, dayNumber: string, dateObj: Date) { const pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}"; - const weekDay = DAYS[dateObj.getDay()]; + const weekDay = t(WEEKDAY_TRANSLATION_IDS[dateObj.getDay()]); return pattern .replace(/{ordinal}/g, ordinal(parseInt(dayNumber))) diff --git a/translations/en/server.json b/translations/en/server.json index 3d8a46e55..68de4c6f0 100644 --- a/translations/en/server.json +++ b/translations/en/server.json @@ -157,5 +157,28 @@ "clipped-from": "This note was originally clipped from {{- url}}", "child-notes": "Child notes:", "no-content": "This note has no content." + }, + "weekdays": { + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday" + }, + "months": { + "january": "January", + "february": "February", + "march": "March", + "april": "April", + "may": "May", + "june": "June", + "july": "July", + "august": "August", + "september": "September", + "october": "October", + "november": "November", + "december": "December" } } diff --git a/translations/ro/server.json b/translations/ro/server.json index b71a9b34d..053823a57 100644 --- a/translations/ro/server.json +++ b/translations/ro/server.json @@ -157,5 +157,28 @@ "clipped-from": "Această notiță a fost decupată inițial de pe {{- url}}", "no-content": "Această notiță nu are conținut.", "parent": "părinte:" + }, + "weekdays": { + "monday": "Luni", + "tuesday": "Marți", + "wednesday": "Miercuri", + "thursday": "Joi", + "friday": "Vineri", + "saturday": "Sâmbătă", + "sunday": "Duminică" + }, + "months": { + "january": "Ianuarie", + "february": "Februarie", + "march": "Martie", + "april": "Aprilie", + "may": "Mai", + "june": "Iunie", + "july": "Iulie", + "august": "August", + "september": "Septembrie", + "october": "Octombrie", + "november": "Noiembrie", + "december": "Decembrie" } } From 9f6f0f5d60c3574dbf967c8adf7b56b211a8ba25 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 23 Oct 2024 19:56:06 +0300 Subject: [PATCH 27/60] server: Update locale when switching language from settings --- src/routes/api/options.ts | 6 ++++++ src/services/i18n.ts | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts index 8f4f661ff..1cd245763 100644 --- a/src/routes/api/options.ts +++ b/src/routes/api/options.ts @@ -5,6 +5,7 @@ import log from "../../services/log.js"; import searchService from "../../services/search/services/search.js"; import ValidationError from "../../errors/validation_error.js"; import { Request } from 'express'; +import { changeLanguage } from "../../services/i18n.js"; // options allowed to be updated directly in the Options dialog const ALLOWED_OPTIONS = new Set([ @@ -108,6 +109,11 @@ function update(name: string, value: string) { optionService.setOption(name, value); + if (name === "locale") { + // This runs asynchronously, so it's not perfect, but it does the trick for now. + changeLanguage(value); + } + return true; } diff --git a/src/services/i18n.ts b/src/services/i18n.ts index 3498045bd..84a707b89 100644 --- a/src/services/i18n.ts +++ b/src/services/i18n.ts @@ -41,4 +41,8 @@ function getCurrentLanguage() { } return language; -} \ No newline at end of file +} + +export function changeLanguage(locale: string) { + return i18next.changeLanguage(locale); +} From 70ebf1a08f6d7cb98d0e127ddf56020107f3ddee Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 23 Oct 2024 20:27:36 +0300 Subject: [PATCH 28/60] client: Fix content size for code editor --- src/public/app/widgets/type_widgets/editable_code.js | 1 + src/public/app/widgets/type_widgets/read_only_code.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/public/app/widgets/type_widgets/editable_code.js b/src/public/app/widgets/type_widgets/editable_code.js index 452fbbf9b..a62f3fafb 100644 --- a/src/public/app/widgets/type_widgets/editable_code.js +++ b/src/public/app/widgets/type_widgets/editable_code.js @@ -25,6 +25,7 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget { doRender() { this.$widget = $(TPL); + this.contentSized(); this.$editor = this.$widget.find('.note-detail-code-editor'); keyboardActionService.setupActionsForElement('code-detail', this.$widget, this); diff --git a/src/public/app/widgets/type_widgets/read_only_code.js b/src/public/app/widgets/type_widgets/read_only_code.js index 4cc2fb5e1..72e2e05e5 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.js +++ b/src/public/app/widgets/type_widgets/read_only_code.js @@ -21,6 +21,7 @@ export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget { doRender() { this.$widget = $(TPL); + this.contentSized(); this.$editor = this.$widget.find('.note-detail-readonly-code-content'); super.doRender(); From 43ef452d44b131a4b332df6cf1afc4be710273a4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 23 Oct 2024 20:33:55 +0300 Subject: [PATCH 29/60] client: Fix error when running script due to translations --- src/public/app/components/entrypoints.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/app/components/entrypoints.js b/src/public/app/components/entrypoints.js index c5fd96e28..6ee06ff65 100644 --- a/src/public/app/components/entrypoints.js +++ b/src/public/app/components/entrypoints.js @@ -9,6 +9,7 @@ import ws from "../services/ws.js"; import bundleService from "../services/bundle.js"; import froca from "../services/froca.js"; import linkService from "../services/link.js"; +import { t } from "../services/i18n.js"; export default class Entrypoints extends Component { constructor() { From 48b0af1bbace65b13d1960eb4366249ccd8e84a6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 24 Oct 2024 18:14:17 +0300 Subject: [PATCH 30/60] client: Stop crash if right widget crashes during render --- src/public/app/widgets/right_panel_widget.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/right_panel_widget.js b/src/public/app/widgets/right_panel_widget.js index a9831fb5a..cb28aac43 100644 --- a/src/public/app/widgets/right_panel_widget.js +++ b/src/public/app/widgets/right_panel_widget.js @@ -1,4 +1,6 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js"; +import toastService from "../services/toast.js"; +import { t } from "../services/i18n.js"; const WIDGET_TPL = `
@@ -54,7 +56,16 @@ class RightPanelWidget extends NoteContextAwareWidget { this.$buttons.append(buttonWidget.render()); } - this.initialized = this.doRenderBody(); + this.initialized = this.doRenderBody().catch(e => { + toastService.showPersistent({ + title: t("toast.widget-error.title"), + icon: "alert", + message: t("toast.widget-error.message", { + title: this.widgetTitle, + message: e.message + }) + }); + }); } /** From 81ca0a3776cc5e3d15dd693a1b41e64ed010b44e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 24 Oct 2024 18:47:16 +0300 Subject: [PATCH 31/60] client: Improve logging for basic sync crash --- src/public/app/services/bundle.js | 1 + src/public/app/widgets/basic_widget.js | 23 +++++++++++++++++++-- src/public/translations/en/translation.json | 3 ++- src/public/translations/es/translation.json | 3 +-- src/public/translations/fr/translation.json | 3 +-- src/public/translations/ro/translation.json | 3 ++- 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/public/app/services/bundle.js b/src/public/app/services/bundle.js index 51348111a..72636e7e5 100644 --- a/src/public/app/services/bundle.js +++ b/src/public/app/services/bundle.js @@ -75,6 +75,7 @@ async function getWidgetBundlesByParent() { try { widget = await executeBundle(bundle); + widget._noteId = bundle.noteId; widgetsByParent.add(widget); } catch (e) { diff --git a/src/public/app/widgets/basic_widget.js b/src/public/app/widgets/basic_widget.js index b60a74e8c..7c7c08c1a 100644 --- a/src/public/app/widgets/basic_widget.js +++ b/src/public/app/widgets/basic_widget.js @@ -1,4 +1,5 @@ import Component from "../components/component.js"; +import froca from "../services/froca.js"; import { t } from "../services/i18n.js"; import toastService from "../services/toast.js"; @@ -85,11 +86,29 @@ class BasicWidget extends Component { try { this.doRender(); } catch (e) { + console.log("Got issue in widget ", this); + console.error(e); + + let noteId = this._noteId; + if (this._noteId) { + froca.getNote(noteId, true).then((note) => { + toastService.showPersistent({ + title: t("toast.widget-error.title"), + icon: "alert", + message: t("toast.widget-error.message-custom", { + id: noteId, + title: note.title, + message: e.message + }) + }); + }); + return; + } + toastService.showPersistent({ title: t("toast.widget-error.title"), icon: "alert", - message: t("toast.widget-error.message", { - title: this.widgetTitle, + message: t("toast.widget-error.message-unknown", { message: e.message }) }); diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 35fb226e5..40891d333 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -16,7 +16,8 @@ }, "widget-error": { "title": "Failed to initialize a widget", - "message": "Widget with title \"{{title}}\" could not be initialized due to:\n\n{{message}}" + "message-custom": "Custom widget from note with ID \"{{id}}\", titled \"{{title}}\" could not be initialized due to:\n\n{{message}}", + "message-unknown": "Unknown widget could not be initialized due to:\n\n{{message}}" } }, "add_link": { diff --git a/src/public/translations/es/translation.json b/src/public/translations/es/translation.json index e2c438475..df9386e77 100644 --- a/src/public/translations/es/translation.json +++ b/src/public/translations/es/translation.json @@ -15,8 +15,7 @@ "message": "Ha ocurrido un error crítico que previene que el cliente de la aplicación inicie:\n\n{{message}}\n\nMuy probablemente es causado por un script que falla de forma inesperada. Intente iniciar la aplicación en modo seguro y atienda el error." }, "widget-error": { - "title": "No se pudo inicializar un widget", - "message": "Widget con título \"{{title}}\" no pudo ser inicializado debido a:\n\n{{message}}" + "title": "No se pudo inicializar un widget" } }, "add_link": { diff --git a/src/public/translations/fr/translation.json b/src/public/translations/fr/translation.json index f95d27bec..4ff9b9920 100644 --- a/src/public/translations/fr/translation.json +++ b/src/public/translations/fr/translation.json @@ -15,8 +15,7 @@ "message": "Une erreur critique s'est produite qui empêche l'application client de démarrer :\n\n{{message}}\n\nCeci est probablement dû à un échec inattendu d'un script. Essayez de démarrer l'application en mode sans échec et de résoudre le problème." }, "widget-error": { - "title": "Impossible d'initialiser un widget", - "message": "Le widget portant le titre \"{{title}}\" n'a pas pu être initialisé en raison de :\n\n{{message}}" + "title": "Impossible d'initialiser un widget" } }, "add_link": { diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index a6bed6d62..51f09a9e1 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1190,7 +1190,8 @@ }, "widget-error": { "message": "Widget-ul intitulat „{{title}}” nu a putut fi inițializat din cauza:\n\n{{message}}", - "title": "Eroare la inițializarea unui widget" + "title": "Eroare la inițializarea unui widget", + "message-custom": "Widget-ul personalizat din notița cu ID-ul „{{id}}”, întitulată ”{{title}}” nu a putut fi inițializată din cauza:\n\n{{message}}" } }, "tray": { From eee088316deeb6319080e3585ceb558576547031 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 24 Oct 2024 20:55:21 +0300 Subject: [PATCH 32/60] client: Improve logging for some bundle errors --- src/public/app/desktop.js | 4 ++-- src/public/app/services/bundle.js | 13 +++++++++++++ src/public/translations/en/translation.json | 4 ++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/public/app/desktop.js b/src/public/app/desktop.js index febb9a8f9..835624d35 100644 --- a/src/public/app/desktop.js +++ b/src/public/app/desktop.js @@ -9,9 +9,9 @@ import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; +await appContext.earlyInit(); + bundleService.getWidgetBundlesByParent().then(async widgetBundles => { - await appContext.earlyInit(); - // A dynamic import is required for layouts since they initialize components which require translations. const DesktopLayout = (await import("./layouts/desktop_layout.js")).default; diff --git a/src/public/app/services/bundle.js b/src/public/app/services/bundle.js index 72636e7e5..279d2d560 100644 --- a/src/public/app/services/bundle.js +++ b/src/public/app/services/bundle.js @@ -3,6 +3,7 @@ import server from "./server.js"; import toastService from "./toast.js"; import froca from "./froca.js"; import utils from "./utils.js"; +import { t } from "./i18n.js"; async function getAndExecuteBundle(noteId, originEntity = null, script = null, params = null) { const bundle = await server.post(`script/bundle/${noteId}`, { @@ -79,6 +80,18 @@ async function getWidgetBundlesByParent() { widgetsByParent.add(widget); } catch (e) { + const noteId = bundle.noteId; + const note = await froca.getNote(noteId); + toastService.showPersistent({ + title: t("toast.bundle-error.title"), + icon: "alert", + message: t("toast.bundle-error.message", { + id: noteId, + title: note.title, + message: e.message + }) + }); + logError("Widget initialization failed: ", e); continue; } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 40891d333..984867d07 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -18,6 +18,10 @@ "title": "Failed to initialize a widget", "message-custom": "Custom widget from note with ID \"{{id}}\", titled \"{{title}}\" could not be initialized due to:\n\n{{message}}", "message-unknown": "Unknown widget could not be initialized due to:\n\n{{message}}" + }, + "bundle-error": { + "title": "Failed to load a custom script", + "message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}" } }, "add_link": { From cb4fe4481f599249144b6cfc2f5addf6b5ac8f5a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 19:57:31 +0300 Subject: [PATCH 33/60] client: Strengthen widget rendering errors detection --- src/public/app/widgets/basic_widget.js | 58 ++++++++++--------- .../app/widgets/containers/container.js | 6 +- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/public/app/widgets/basic_widget.js b/src/public/app/widgets/basic_widget.js index 7c7c08c1a..4dd580bd2 100644 --- a/src/public/app/widgets/basic_widget.js +++ b/src/public/app/widgets/basic_widget.js @@ -85,33 +85,8 @@ class BasicWidget extends Component { render() { try { this.doRender(); - } catch (e) { - console.log("Got issue in widget ", this); - console.error(e); - - let noteId = this._noteId; - if (this._noteId) { - froca.getNote(noteId, true).then((note) => { - toastService.showPersistent({ - title: t("toast.widget-error.title"), - icon: "alert", - message: t("toast.widget-error.message-custom", { - id: noteId, - title: note.title, - message: e.message - }) - }); - }); - return; - } - - toastService.showPersistent({ - title: t("toast.widget-error.title"), - icon: "alert", - message: t("toast.widget-error.message-unknown", { - message: e.message - }) - }); + } catch (e) { + this.logRenderingError(e); } this.$widget.attr('data-component-id', this.componentId); @@ -150,6 +125,35 @@ class BasicWidget extends Component { return this.$widget; } + logRenderingError(e) { + console.log("Got issue in widget ", this); + console.error(e); + + let noteId = this._noteId; + if (this._noteId) { + froca.getNote(noteId, true).then((note) => { + toastService.showPersistent({ + title: t("toast.widget-error.title"), + icon: "alert", + message: t("toast.widget-error.message-custom", { + id: noteId, + title: note.title, + message: e.message + }) + }); + }); + return; + } + + toastService.showPersistent({ + title: t("toast.widget-error.title"), + icon: "alert", + message: t("toast.widget-error.message-unknown", { + message: e.message + }) + }); + } + /** * Indicates if the widget is enabled. Widgets are enabled by default. Generally setting this to `false` will cause the widget not to be displayed, however it will still be available on the DOM but hidden. * @returns diff --git a/src/public/app/widgets/containers/container.js b/src/public/app/widgets/containers/container.js index ba47a8db0..07c759bb0 100644 --- a/src/public/app/widgets/containers/container.js +++ b/src/public/app/widgets/containers/container.js @@ -8,7 +8,11 @@ export default class Container extends BasicWidget { renderChildren() { for (const widget of this.children) { - this.$widget.append(widget.render()); + try { + this.$widget.append(widget.render()); + } catch (e) { + widget.logRenderingError(e); + } } } } From 560467bdbaf3884ccb195943b2293e7dbd04d7e1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 19:57:40 +0300 Subject: [PATCH 34/60] client: Log uncaught promise errors --- src/public/app/services/glob.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/public/app/services/glob.js b/src/public/app/services/glob.js index 7177a6f95..e3603a9e6 100644 --- a/src/public/app/services/glob.js +++ b/src/public/app/services/glob.js @@ -64,6 +64,28 @@ function setupGlobs() { return false; }; + window.addEventListener("unhandledrejection", (e) => { + const string = e.reason.message.toLowerCase(); + + let message = "Uncaught error: "; + + if (string.includes("script error")) { + message += 'No details available'; + } else { + message += [ + `Message: ${e.reason.message}`, + `Line: ${e.reason.lineNumber}`, + `Column: ${e.reason.columnNumber}`, + `Error object: ${JSON.stringify(e.reason)}`, + `Stack: ${e.reason && e.reason.stack}` + ].join(', '); + } + + ws.logError(message); + + return false; + }); + for (const appCssNoteId of glob.appCssNoteIds || []) { libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`, false); } From 70d55097ee66ae5fe226f9554c32030a6fd59b7d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 20:15:12 +0300 Subject: [PATCH 35/60] client: Fix crash if note tree fails to find a child note --- src/public/app/widgets/note_tree.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index c79e79ea8..cefba22a2 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -666,8 +666,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { } const node = this.prepareNode(branch); - - noteList.push(node); + if (node) { + noteList.push(node); + } } return noteList; @@ -709,7 +710,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { const note = branch.getNoteFromCache(); if (!note) { - throw new Error(`Branch '${branch.branchId}' has no child note '${branch.noteId}'`); + console.warn(`Branch '${branch.branchId}' has no child note '${branch.noteId}'`); + return null; } const title = `${branch.prefix ? (`${branch.prefix} - `) : ""}${note.title}`; From 03a23d15f9a59e369c2750e45f34a628139a0ea0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 20:22:10 +0300 Subject: [PATCH 36/60] client: Fix double errors if not returning a widget --- src/public/app/services/bundle.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/public/app/services/bundle.js b/src/public/app/services/bundle.js index 279d2d560..e0a81eee4 100644 --- a/src/public/app/services/bundle.js +++ b/src/public/app/services/bundle.js @@ -76,10 +76,11 @@ async function getWidgetBundlesByParent() { try { widget = await executeBundle(bundle); - widget._noteId = bundle.noteId; - widgetsByParent.add(widget); - } - catch (e) { + if (widget) { + widget._noteId = bundle.noteId; + widgetsByParent.add(widget); + } + } catch (e) { const noteId = bundle.noteId; const note = await froca.getNote(noteId); toastService.showPersistent({ From 27f07ee6043fbfc30aac6fa0198adfb5baefe343 Mon Sep 17 00:00:00 2001 From: hasecilu Date: Fri, 25 Oct 2024 10:45:45 -0600 Subject: [PATCH 37/60] i18n: Update Spanish translations --- src/public/translations/es/translation.json | 117 +++++-- translations/es/server.json | 341 +++++++++++--------- 2 files changed, 277 insertions(+), 181 deletions(-) diff --git a/src/public/translations/es/translation.json b/src/public/translations/es/translation.json index df9386e77..330d84c14 100644 --- a/src/public/translations/es/translation.json +++ b/src/public/translations/es/translation.json @@ -15,7 +15,13 @@ "message": "Ha ocurrido un error crítico que previene que el cliente de la aplicación inicie:\n\n{{message}}\n\nMuy probablemente es causado por un script que falla de forma inesperada. Intente iniciar la aplicación en modo seguro y atienda el error." }, "widget-error": { - "title": "No se pudo inicializar un widget" + "title": "No se pudo inicializar un widget", + "message-custom": "El widget personalizado de la nota con ID \"{{id}}\", titulada \"{{title}}\" no pudo ser inicializado debido a:\n\n{{message}}", + "message-unknown": "Un widget no pudo ser inicializado debido a:\n\n{{message}}" + }, + "bundle-error": { + "title": "Hubo un fallo al cargar un script personalizado", + "message": "El script de la nota con ID \"{{id}}\", titulado \"{{title}}\" no pudo ser ejecutado debido a:\n\n{{message}}" } }, "add_link": { @@ -165,7 +171,8 @@ "textImportedAsText": "Importar HTML, Markdown y TXT como notas de texto si no está claro en los metadatos", "codeImportedAsCode": "Importar archivos de código reconocidos (por ejemplo, .json) como notas de código si no están claros en los metadatos", "replaceUnderscoresWithSpaces": "Reemplazar guiones bajos con espacios en nombres de notas importadas", - "import": "Importar" + "import": "Importar", + "failed": "La importación falló: {{message}}." }, "include_note": { "dialog_title": "Incluir nota", @@ -329,9 +336,9 @@ "run_on_instance": "Definir en qué instancia de Trilium se debe ejecutar esto. Predeterminado para todas las instancias.", "run_at_hour": "¿A qué hora debería funcionar? Debe usarse junto con #run=hourly. Se puede definir varias veces para varias ejecuciones durante el día.", "disable_inclusion": "los scripts con esta etiqueta no se incluirán en la ejecución del script principal.", - "sorted": "mantiene las notas hijo ordenadas alfabéticamente por título", + "sorted": "mantiene las subnotas ordenadas alfabéticamente por título", "sort_direction": "ASC (el valor predeterminado) o DESC", - "sort_folders_first": "Las carpetas (notas con hijos) deben ordenarse en la parte superior", + "sort_folders_first": "Las carpetas (notas con subnotas) deben ordenarse en la parte superior", "top": "mantener la nota dada en la parte superior de su padre (se aplica solo en padres ordenados)", "hide_promoted_attributes": "Ocultar atributos promovidos en esta nota", "read_only": "el editor está en modo de sólo lectura. Funciona sólo para notas de texto y código.", @@ -349,9 +356,9 @@ "workspace_tab_background_color": "color CSS utilizado en la pestaña de nota cuando se ancla a esta nota", "workspace_calendar_root": "Define la raíz del calendario por cada espacio de trabajo", "workspace_template": "Esta nota aparecerá en la selección de plantillas disponibles al crear una nueva nota, pero solo cuando se levante a un espacio de trabajo que contenga esta plantilla", - "search_home": "se crearán nuevas notas de búsqueda como hijas de esta nota", - "workspace_search_home": "se crearán nuevas notas de búsqueda como hijo de esta nota cuando se anclan a algún antecesor de esta nota del espacio de trabajo", - "inbox": "ubicación predeterminada de la bandeja de entrada para nuevas notas - cuando crea una nota usando el botón \"nueva nota\" en la barra lateral, las notas serán creadas como notas hijo de la nota marcada con la etiqueta #inbox.", + "search_home": "se crearán nuevas notas de búsqueda como subnotas de esta nota", + "workspace_search_home": "se crearán nuevas notas de búsqueda como subnotas de esta nota cuando se anclan a algún antecesor de esta nota del espacio de trabajo", + "inbox": "ubicación predeterminada de la bandeja de entrada para nuevas notas - cuando crea una nota usando el botón \"nueva nota\" en la barra lateral, las notas serán creadas como subnotas de la nota marcada con la etiqueta #inbox.", "workspace_inbox": "ubicación predeterminada de la bandeja de entrada para nuevas notas cuando se anclan a algún antecesor de esta nota del espacio de trabajo", "sql_console_home": "ubicación predeterminada de las notas de la consola SQL", "bookmark_folder": "la nota con esta etiqueta aparecerá en los marcadores como carpeta (permitiendo el acceso a sus elementos hijos).", @@ -367,7 +374,7 @@ "share_index": "tenga en cuenta que con esto esta etiqueta enumerará todas las raíces de las notas compartidas", "display_relations": "nombres de relaciones delimitados por comas que deben mostrarse. Todos los demás estarán ocultos.", "hide_relations": "nombres de relaciones delimitados por comas que deben ocultarse. Se mostrarán todos los demás.", - "title_template": "título por defecto de notas creadas como hijo de esta nota. El valor es evaluado como una cadena de JavaScript \n y por lo tanto puede ser enriquecida con contenido dinámico vía las variables inyectadas now y parentNote. Ejemplos:\n \n
    \n
  • trabajos literarios de ${parentNote.getLabelValue('authorName')}
  • \n
  • Registro para ${now.format('YYYY-MM-DD HH:mm:ss')}
  • \n
\n \n Consulte la wiki para obtener más detalles, documentación de la API para parentNote y now para más detalles.", + "title_template": "título por defecto de notas creadas como subnota de esta nota. El valor es evaluado como una cadena de JavaScript \n y por lo tanto puede ser enriquecida con contenido dinámico vía las variables inyectadas now y parentNote. Ejemplos:\n \n
    \n
  • trabajos literarios de ${parentNote.getLabelValue('authorName')}
  • \n
  • Registro para ${now.format('YYYY-MM-DD HH:mm:ss')}
  • \n
\n \n Consulte la wiki para obtener más detalles, documentación de la API para parentNote y now para más detalles.", "template": "Esta nota aparecerá en la selección de plantillas disponibles al crear una nueva nota", "toc": "#toc o #toc=show forzará que se muestre la tabla de contenido, #toc=hide forzará a ocultarla. Si la etiqueta no existe, se observa la configuración global", "color": "define el color de la nota en el árbol de notas, enlaces, etc. Utilice cualquier valor de color CSS válido como 'red' o #a13d5f", @@ -631,7 +638,10 @@ "export_note": "Exportar nota", "delete_note": "Eliminar nota", "print_note": "Imprimir nota", - "save_revision": "Guardar revisión" + "save_revision": "Guardar revisión", + "convert_into_attachment_failed": "La conversión de nota '{{title}}' falló.", + "convert_into_attachment_successful": "La nota '{{title}}' ha sido convertida a un archivo adjunto.", + "convert_into_attachment_prompt": "¿Está seguro que desea convertir la nota '{{title}}' en un archivo adjunto de la nota padre?" }, "onclick_button": { "no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido" @@ -669,7 +679,7 @@ "button_title": "Exportar diagrama como SVG" }, "relation_map_buttons": { - "create_child_note_title": "Crear una nueva nota hijo y agregarla a este mapa de relaciones", + "create_child_note_title": "Crear una nueva subnota y agregarla a este mapa de relaciones", "reset_pan_zoom_title": "Restablecer la panorámica y el zoom a las coordenadas y ampliación iniciales", "zoom_in_title": "Acercar", "zoom_out_title": "Alejar" @@ -680,7 +690,7 @@ "relation": "relación" }, "mobile_detail_menu": { - "insert_child_note": "Insertar nota hijo", + "insert_child_note": "Insertar subnota", "delete_this_note": "Eliminar esta nota", "error_cannot_get_branch_id": "No se puede obtener el branchID del notePath '{{notePath}}'", "error_unrecognized_command": "Comando no reconocido {{command}}" @@ -701,7 +711,7 @@ "grid": "Cuadrícula", "list": "Lista", "collapse_all_notes": "Contraer todas las notas", - "expand_all_children": "Ampliar todos los hijos", + "expand_all_children": "Ampliar todas las subnotas", "collapse": "Colapsar", "expand": "Expandir", "book_properties": "Propiedades del libro", @@ -855,7 +865,7 @@ "content_and_attachments_size": "Tenga en cuenta el tamaño del contenido, incluidos los archivos adjuntos", "content_and_attachments_and_revisions_size": "Tenga en cuenta el tamaño del contenido, incluidos los archivos adjuntos y las revisiones", "revision_count": "Número de revisiones", - "children_count": "Notas sobre el número de hijos", + "children_count": "Número de subnotas", "parent_count": "Número de clones", "owned_label_count": "Número de etiquetas", "owned_relation_count": "Número de relaciones", @@ -891,7 +901,7 @@ "attachment_detail": { "open_help_page": "Abrir página de ayuda en archivos adjuntos", "owning_note": "Nota dueña: ", - "you_can_also_open": ", también puedes abri el ", + "you_can_also_open": ", también puede abrir el ", "list_of_all_attachments": "Lista de todos los archivos adjuntos", "attachment_deleted": "Este archivo adjunto ha sido eliminado." }, @@ -902,7 +912,7 @@ "no_attachments": "Esta nota no tiene archivos adjuntos." }, "book": { - "no_children_help": "Esta nota de tipo libro no tieneninguna nota hijo así que no hay nada que mostrar. Véa la wiki para más detalles." + "no_children_help": "Esta nota de tipo libro no tieneninguna subnota así que no hay nada que mostrar. Véa la wiki para más detalles." }, "editable_code": { "placeholder": "Escriba el contenido de su nota de código aquí..." @@ -920,7 +930,15 @@ }, "protected_session": { "enter_password_instruction": "Para mostrar una nota protegida es necesario ingresar su contraseña:", - "start_session_button": "Iniciar sesión protegida" + "start_session_button": "Iniciar sesión protegida", + "started": "La sesión protegida ha iniciado.", + "wrong_password": "Contraseña incorrecta.", + "protecting-finished-successfully": "La protección finalizó exitosamente.", + "unprotecting-finished-successfully": "La desprotección finalizó exitosamente.", + "protecting-in-progress": "Protección en progreso: {{count}}", + "unprotecting-in-progress-count": "Desprotección en progreso: {{count}}", + "protecting-title": "Estado de protección", + "unprotecting-title": "Estado de desprotección" }, "relation_map": { "open_in_new_tab": "Abrir en nueva pestaña", @@ -991,7 +1009,9 @@ "fill_entity_changes_button": "Llenar registros de cambios de entidad", "full_sync_triggered": "Sincronización completa activada", "filling_entity_changes": "Rellenar filas de cambios de entidad...", - "sync_rows_filled_successfully": "Sincronizar filas completadas correctamente" + "sync_rows_filled_successfully": "Sincronizar filas completadas correctamente", + "finished-successfully": "La sincronización finalizó exitosamente.", + "failed": "La sincronización falló: {{message}}" }, "vacuum_database": { "title": "Limpiar base de datos", @@ -1137,7 +1157,7 @@ }, "table_of_contents": { "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. Puedes 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:", "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')." }, @@ -1286,7 +1306,7 @@ "open-in-a-new-tab": "Abrir en nueva pestaña", "open-in-a-new-split": "Abrir en nueva división", "insert-note-after": "Insertar nota después de", - "insert-child-note": "Insertar nota hijo", + "insert-child-note": "Insertar subnota", "delete": "Eliminar", "search-in-subtree": "Buscar en subárbol", "hoist-note": "Anclar nota", @@ -1310,7 +1330,9 @@ "duplicate-subtree": "Duplicar subárbol", "export": "Exportar", "import-into-note": "Importar a nota", - "apply-bulk-actions": "Aplicar acciones en lote" + "apply-bulk-actions": "Aplicar acciones en lote", + "converted-to-attachments": "{{count}} notas han sido convertidas en archivos adjuntos.", + "convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres?" }, "shared_info": { "shared_publicly": "Esta nota está compartida públicamente en", @@ -1333,7 +1355,8 @@ "image": "Imagen", "launcher": "Lanzador", "doc": "Doc", - "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?" }, "protect_note": { "toggle-on": "Proteger la nota", @@ -1379,7 +1402,9 @@ "hide-archived-notes": "Ocultar notas archivadas", "automatically-collapse-notes": "Colapsar notas automaticamente", "automatically-collapse-notes-title": "Las notas serán colapsadas después de un periodo de inactividad para despejar el árbol.", - "save-changes": "Guardar y aplicar cambios" + "save-changes": "Guardar y aplicar cambios", + "auto-collapsing-notes-after-inactivity": "Colapsando notas automáticamente después de inactividad...", + "saved-search-note-refreshed": "La nota de búsqueda guardada fue recargada." }, "title_bar_buttons": { "window-on-top": "Mantener esta ventana en la parte superior." @@ -1423,5 +1448,53 @@ }, "app_context": { "please_wait_for_save": "Por favor espere algunos segundos a que se termine de guardar, después intente de nuevo." + }, + "note_create": { + "duplicated": "La nota \"{{title}}\" ha sido duplicada." + }, + "image": { + "copied-to-clipboard": "Una referencia a la imagen ha sido copiada al portapapeles. Esta puede ser pegada en cualquier nota de texto.", + "cannot-copy": "No se pudo copiar la referencia de imagen al portapapeles." + }, + "clipboard": { + "cut": "La(s) notas(s) han sido cortadas al portapapeles.", + "copied": "La(s) notas(s) han sido copiadas al portapapeles." + }, + "entrypoints": { + "note-revision-created": "Una revisión de nota ha sido creada.", + "note-executed": "Nota ejecutada.", + "sql-error": "Ocurrió un error al ejecutar la consulta SQL: {{message}}" + }, + "branches": { + "cannot-move-notes-here": "No se pueden mover notas aquí.", + "delete-status": "Estado de eliminación", + "delete-notes-in-progress": "Eliminación de notas en progreso: {{count}}", + "delete-finished-successfully": "La eliminación finalizó exitosamente.", + "undeleting-notes-in-progress": "Recuperación de notas en progreso: {{count}}", + "undeleting-notes-finished-successfully": "La recuperación de notas finalizó exitosamente." + }, + "frontend_script_api": { + "async_warning": "Está pasando una función asíncrona a `api.runOnBackend ()` que probablemente no funcionará como pretendía.", + "sync_warning": "Estás pasando una función sincrónica a `api.runasynconbackendwithmanualTransactionHandling ()`, \\ n while debería usar `api.runonbackend ()` en su lugar." + }, + "ws": { + "sync-check-failed": "¡La comprobación de sincronización falló!", + "consistency-checks-failed": "¡Las comprobaciones de consistencia fallaron! Vea los registros para más detalles.", + "encountered-error": "Error encontrado \"{{message}}\", compruebe la consola." + }, + "hoisted_note": { + "confirm_unhoisting": "La nota requerida '{{requestedNote}}' está fuera del subárbol de la nota anclada '{{hoistedNote}}' y debe desanclarla para acceder a la nota. ¿Desea proceder con el desanclaje?" + }, + "launcher_context_menu": { + "reset_launcher_confirm": "¿Realmente desea restaurar \"{{title}}\"? Todos los datos / ajustes en esta nota (y sus subnotas) se van a perder y el lanzador regresará a su ubicación original.", + "add-note-launcher": "Agregar un lanzador de nota", + "add-script-launcher": "Agregar un lanzador de script", + "add-custom-widget": "Agregar un widget personalizado", + "add-spacer": "Agregar espaciador", + "delete": "Eliminar", + "reset": "Restaurar", + "move-to-visible-launchers": "Mover a lanzadores visibles", + "move-to-available-launchers": "Mover a lanzadores disponibles", + "duplicate-launcher": "Duplicar lanzador" } } diff --git a/translations/es/server.json b/translations/es/server.json index 654e5fd6b..ab4c28147 100644 --- a/translations/es/server.json +++ b/translations/es/server.json @@ -1,161 +1,184 @@ { - "keyboard_actions": { - "open-jump-to-note-dialog": "Abrir cuadro de diálogo \"Saltar a nota\"", - "search-in-subtree": "Buscar notas en el subárbol de la nota activa", - "expand-subtree": "Expandir el subárbol de la nota actual", - "collapse-tree": "Colapsa el árbol de notas completo", - "collapse-subtree": "Colapsa el subárbol de la nota actual", - "sort-child-notes": "Ordenar notas hijas", - "creating-and-moving-notes": "Creando y moviendo notas", - "create-note-into-inbox": "Crear una nota en la bandeja de entrada (si está definida) o nota del día", - "delete-note": "Eliminar nota", - "move-note-up": "Mover nota hacia arriba", - "move-note-down": "Mover nota hacia abajo", - "move-note-up-in-hierarchy": "Mover nota hacia arriba en la jerarquía", - "move-note-down-in-hierarchy": "Mover nota hacia abajo en la jerarquía", - "edit-note-title": "Saltar del árbol al detalle de la nota y editar el título", - "edit-branch-prefix": "Mostrar cuadro de diálogo Editar prefijo de rama", - "note-clipboard": "Portapapeles de notas", - "copy-notes-to-clipboard": "Copiar las notas seleccionadas al portapapeles", - "paste-notes-from-clipboard": "Pegar las notas del portapapeles en una nota activa", - "cut-notes-to-clipboard": "Cortar las notas seleccionadas al portapapeles", - "select-all-notes-in-parent": "Seleccionar todas las notas del nivel de la nota actual", - "add-note-above-to-the-selection": "Agregar nota arriba de la selección", - "add-note-below-to-selection": "Agregar nota arriba de la selección", - "duplicate-subtree": "Duplicar subárbol", - "tabs-and-windows": "Pestañas y ventanas", - "open-new-tab": "Abre una nueva pestaña", - "close-active-tab": "Cierra la pestaña activa", - "reopen-last-tab": "Vuelve a abrir la última pestaña cerrada", - "activate-next-tab": "Activa la pestaña de la derecha", - "activate-previous-tab": "Activa la pestaña de la izquierda", - "open-new-window": "Abrir nueva ventana vacía", - "toggle-tray": "Muestra/Oculta la aplicación en la bandeja del sistema", - "first-tab": "Activa la primera pestaña de la lista", - "second-tab": "Activa la segunda pestaña de la lista", - "third-tab": "Activa la tercera pestaña de la lista", - "fourth-tab": "Activa la cuarta pestaña de la lista", - "fifth-tab": "Activa la quinta pestaña de la lista", - "sixth-tab": "Activa la sexta pestaña de la lista", - "seventh-tab": "Activa la séptima pestaña de la lista", - "eight-tab": "Activa la octava pestaña de la lista", - "ninth-tab": "Activa la novena pestaña de la lista", - "last-tab": "Activa la última pestaña de la lista", - "dialogs": "Diálogos", - "show-note-source": "Muestra el cuadro de diálogo Fuente de nota", - "show-options": "Muestra el cuadro de diálogo Opciones", - "show-revisions": "Muestra el cuadro de diálogo Revisiones de notas", - "show-recent-changes": "Muestra el cuadro de diálogo Cambios recientes", - "show-sql-console": "Muestra el cuadro de diálogo Consola SQL", - "show-backend-log": "Muestra el cuadro de diálogo Registro de backend", - "text-note-operations": "Operaciones de notas de texto", - "add-link-to-text": "Abrir cuadro de diálogo para agregar un enlace al texto", - "follow-link-under-cursor": "Seguir el enlace dentro del cual se coloca el cursor", - "insert-date-and-time-to-text": "Insertar fecha y hora actuales en el texto", - "paste-markdown-into-text": "Pega Markdown del portapapeles en la nota de texto", - "cut-into-note": "Corta la selección de la nota actual y crea una subnota con el texto seleccionado", - "add-include-note-to-text": "Abre el cuadro de diálogo para incluir una nota", - "edit-readonly-note": "Editar una nota de sólo lectura", - "attributes-labels-and-relations": "Atributos (etiquetas y relaciones)", - "add-new-label": "Crear nueva etiqueta", - "create-new-relation": "Crear nueva relación", - "ribbon-tabs": "Pestañas de cinta", - "toggle-basic-properties": "Alternar propiedades básicas", - "toggle-file-properties": "Alternar propiedades de archivo", - "toggle-image-properties": "Alternar propiedades de imagen", - "toggle-owned-attributes": "Alternar atributos de propiedad", - "toggle-inherited-attributes": "Alternar atributos heredados", - "toggle-promoted-attributes": "Alternar atributos promocionados", - "toggle-link-map": "Alternar mapa de enlaces", - "toggle-note-info": "Alternar información de nota", - "toggle-note-paths": "Alternar rutas de notas", - "toggle-similar-notes": "Alternar notas similares", - "other": "Otro", - "toggle-right-pane": "Alternar la visualización del panel derecho, que incluye la tabla de contenidos y aspectos destacados", - "print-active-note": "Imprimir nota activa", - "open-note-externally": "Abrir nota como un archivo con la aplicación predeterminada", - "render-active-note": "Renderizar (volver a renderizar) nota activa", - "run-active-note": "Ejecutar nota de código JavaScript activa (frontend/backend)", - "toggle-note-hoisting": "Alterna la elevación de la nota activa", - "unhoist": "Bajar desde cualquier lugar", - "reload-frontend-app": "Recargar frontend de la aplicación", - "open-dev-tools": "Abrir herramientas de desarrollo", - "toggle-left-note-tree-panel": "Alternar panel izquierdo (árbol de notas)", - "toggle-full-screen": "Alternar pantalla completa", - "zoom-out": "Alejar", - "zoom-in": "Acercar", - "note-navigation": "Navegación de notas", - "reset-zoom-level": "Restablecer nivel de zoom", - "copy-without-formatting": "Copiar el texto seleccionado sin formatear", - "force-save-revision": "Forzar la creación/guardado de una nueva revisión de nota de la nota activa", - "show-help": "Muestra ayuda/hoja de referencia integrada", - "toggle-book-properties": "Alternar propiedades del libro" - }, - "login": { - "title": "Iniciar sesión", - "heading": "Iniciar sesión en Trillium", - "incorrect-password": "La contraseña es incorrecta. Por favor inténtalo de nuevo.", - "password": "Contraseña", - "remember-me": "Recordarme", - "button": "Iniciar sesión" - }, - "set_password": { - "heading": "Establecer contraseña", - "description": "Antes de poder comenzar a usar Trilium desde la web, primero debe establecer una contraseña. Luego utilizará esta contraseña para iniciar sesión.", - "password": "Contraseña", - "password-confirmation": "Confirmación de contraseña", - "button": "Establecer contraseña" - }, - "javascript-required": "Trilium requiere que JavaScript esté habilitado.", - "setup": { - "heading": "Configuración de TrilliumNext Notes", - "new-document": "Soy un usuario nuevo y quiero crear un nuevo documento de Trilium para mis notas", - "sync-from-desktop": "Ya tengo una instancia de escritorio y quiero configurar la sincronización con ella", - "sync-from-server": "Ya tengo una instancia de servidor y quiero configurar la sincronización con ella", - "next": "Siguiente", - "init-in-progress": "Inicialización del documento en curso", - "redirecting": "En breve será redirigido a la aplicación.", - "title": "Configuración" - }, - "setup_sync-from-desktop": { - "heading": "Sincronizar desde el escritorio", - "description": "Esta configuración debe iniciarse desde la instancia de escritorio:", - "step1": "Abra su instancia de escritorio de TriliumNext Notes.", - "step2": "En el menú Trilium, dé clic en Opciones.", - "step3": "Dé clic en la categoría Sincronizar.", - "step4": "Cambie la dirección de la instancia del servidor a: {{- host}} y dé clic en Guardar.", - "step5": "Dé clic en el botón \"Probar sincronización\" para verificar que la conexión fue exitosa.", - "step6": "Una vez que haya completado estos pasos, dé clic en {{- link}}.", - "step6-here": "aquí" - }, - "setup_sync-from-server": { - "heading": "Sincronización desde el servidor", - "instructions": "Por favor, ingrese la dirección y las credenciales del servidor Trilium a continuación. Esto descargará todo el documento de Trilium desde el servidor y configurará la sincronización. Dependiendo del tamaño del documento y de la velocidad de su conexión, esto puede tardar un poco.", - "server-host": "Dirección del servidor Trilium", - "server-host-placeholder": "https://:", - "proxy-server": "Servidor proxy (opcional)", - "proxy-server-placeholder": "https://:", - "note": "Nota:", - "proxy-instruction": "Si deja la configuración de proxy en blanco, se utilizará el proxy del sistema (aplica únicamente a la aplicación de escritorio)", - "password": "Contraseña", - "password-placeholder": "Contraseña", - "back": "Atrás", - "finish-setup": "Finalizar la configuración" - }, - "setup_sync-in-progress": { - "heading": "Sincronización en progreso", - "successful": "La sincronización se ha configurado correctamente. La sincronización inicial tardará algún tiempo en finalizar. Una vez hecho esto, será redirigido a la página de inicio de sesión.", - "outstanding-items": "Elementos de sincronización destacados:", - "outstanding-items-default": "N/A" - }, - "share_404": { - "title": "No encontrado", - "heading": "No encontrado" - }, - "share_page": { - "parent": "padre:", - "clipped-from": "Esta nota fue recortada originalmente de {{- url}}", - "child-notes": "Notas hijo:", - "no-content": "Esta nota no tiene contenido." - } + "keyboard_actions": { + "open-jump-to-note-dialog": "Abrir cuadro de diálogo \"Saltar a nota\"", + "search-in-subtree": "Buscar notas en el subárbol de la nota activa", + "expand-subtree": "Expandir el subárbol de la nota actual", + "collapse-tree": "Colapsa el árbol de notas completo", + "collapse-subtree": "Colapsa el subárbol de la nota actual", + "sort-child-notes": "Ordenar subnotas", + "creating-and-moving-notes": "Creando y moviendo notas", + "create-note-into-inbox": "Crear una nota en la bandeja de entrada (si está definida) o nota del día", + "delete-note": "Eliminar nota", + "move-note-up": "Mover nota hacia arriba", + "move-note-down": "Mover nota hacia abajo", + "move-note-up-in-hierarchy": "Mover nota hacia arriba en la jerarquía", + "move-note-down-in-hierarchy": "Mover nota hacia abajo en la jerarquía", + "edit-note-title": "Saltar del árbol al detalle de la nota y editar el título", + "edit-branch-prefix": "Mostrar cuadro de diálogo Editar prefijo de rama", + "note-clipboard": "Portapapeles de notas", + "copy-notes-to-clipboard": "Copiar las notas seleccionadas al portapapeles", + "paste-notes-from-clipboard": "Pegar las notas del portapapeles en una nota activa", + "cut-notes-to-clipboard": "Cortar las notas seleccionadas al portapapeles", + "select-all-notes-in-parent": "Seleccionar todas las notas del nivel de la nota actual", + "add-note-above-to-the-selection": "Agregar nota arriba de la selección", + "add-note-below-to-selection": "Agregar nota arriba de la selección", + "duplicate-subtree": "Duplicar subárbol", + "tabs-and-windows": "Pestañas y ventanas", + "open-new-tab": "Abre una nueva pestaña", + "close-active-tab": "Cierra la pestaña activa", + "reopen-last-tab": "Vuelve a abrir la última pestaña cerrada", + "activate-next-tab": "Activa la pestaña de la derecha", + "activate-previous-tab": "Activa la pestaña de la izquierda", + "open-new-window": "Abrir nueva ventana vacía", + "toggle-tray": "Muestra/Oculta la aplicación en la bandeja del sistema", + "first-tab": "Activa la primera pestaña de la lista", + "second-tab": "Activa la segunda pestaña de la lista", + "third-tab": "Activa la tercera pestaña de la lista", + "fourth-tab": "Activa la cuarta pestaña de la lista", + "fifth-tab": "Activa la quinta pestaña de la lista", + "sixth-tab": "Activa la sexta pestaña de la lista", + "seventh-tab": "Activa la séptima pestaña de la lista", + "eight-tab": "Activa la octava pestaña de la lista", + "ninth-tab": "Activa la novena pestaña de la lista", + "last-tab": "Activa la última pestaña de la lista", + "dialogs": "Diálogos", + "show-note-source": "Muestra el cuadro de diálogo Fuente de nota", + "show-options": "Muestra el cuadro de diálogo Opciones", + "show-revisions": "Muestra el cuadro de diálogo Revisiones de notas", + "show-recent-changes": "Muestra el cuadro de diálogo Cambios recientes", + "show-sql-console": "Muestra el cuadro de diálogo Consola SQL", + "show-backend-log": "Muestra el cuadro de diálogo Registro de backend", + "text-note-operations": "Operaciones de notas de texto", + "add-link-to-text": "Abrir cuadro de diálogo para agregar un enlace al texto", + "follow-link-under-cursor": "Seguir el enlace dentro del cual se coloca el cursor", + "insert-date-and-time-to-text": "Insertar fecha y hora actuales en el texto", + "paste-markdown-into-text": "Pega Markdown del portapapeles en la nota de texto", + "cut-into-note": "Corta la selección de la nota actual y crea una subnota con el texto seleccionado", + "add-include-note-to-text": "Abre el cuadro de diálogo para incluir una nota", + "edit-readonly-note": "Editar una nota de sólo lectura", + "attributes-labels-and-relations": "Atributos (etiquetas y relaciones)", + "add-new-label": "Crear nueva etiqueta", + "create-new-relation": "Crear nueva relación", + "ribbon-tabs": "Pestañas de cinta", + "toggle-basic-properties": "Alternar propiedades básicas", + "toggle-file-properties": "Alternar propiedades de archivo", + "toggle-image-properties": "Alternar propiedades de imagen", + "toggle-owned-attributes": "Alternar atributos de propiedad", + "toggle-inherited-attributes": "Alternar atributos heredados", + "toggle-promoted-attributes": "Alternar atributos promocionados", + "toggle-link-map": "Alternar mapa de enlaces", + "toggle-note-info": "Alternar información de nota", + "toggle-note-paths": "Alternar rutas de notas", + "toggle-similar-notes": "Alternar notas similares", + "other": "Otro", + "toggle-right-pane": "Alternar la visualización del panel derecho, que incluye la tabla de contenidos y aspectos destacados", + "print-active-note": "Imprimir nota activa", + "open-note-externally": "Abrir nota como un archivo con la aplicación predeterminada", + "render-active-note": "Renderizar (volver a renderizar) nota activa", + "run-active-note": "Ejecutar nota de código JavaScript activa (frontend/backend)", + "toggle-note-hoisting": "Alterna la elevación de la nota activa", + "unhoist": "Bajar desde cualquier lugar", + "reload-frontend-app": "Recargar frontend de la aplicación", + "open-dev-tools": "Abrir herramientas de desarrollo", + "toggle-left-note-tree-panel": "Alternar panel izquierdo (árbol de notas)", + "toggle-full-screen": "Alternar pantalla completa", + "zoom-out": "Alejar", + "zoom-in": "Acercar", + "note-navigation": "Navegación de notas", + "reset-zoom-level": "Restablecer nivel de zoom", + "copy-without-formatting": "Copiar el texto seleccionado sin formatear", + "force-save-revision": "Forzar la creación/guardado de una nueva revisión de nota de la nota activa", + "show-help": "Muestra ayuda/hoja de referencia integrada", + "toggle-book-properties": "Alternar propiedades del libro" + }, + "login": { + "title": "Iniciar sesión", + "heading": "Iniciar sesión en Trillium", + "incorrect-password": "La contraseña es incorrecta. Por favor inténtalo de nuevo.", + "password": "Contraseña", + "remember-me": "Recordarme", + "button": "Iniciar sesión" + }, + "set_password": { + "heading": "Establecer contraseña", + "description": "Antes de poder comenzar a usar Trilium desde la web, primero debe establecer una contraseña. Luego utilizará esta contraseña para iniciar sesión.", + "password": "Contraseña", + "password-confirmation": "Confirmación de contraseña", + "button": "Establecer contraseña" + }, + "javascript-required": "Trilium requiere que JavaScript esté habilitado.", + "setup": { + "heading": "Configuración de TrilliumNext Notes", + "new-document": "Soy un usuario nuevo y quiero crear un nuevo documento de Trilium para mis notas", + "sync-from-desktop": "Ya tengo una instancia de escritorio y quiero configurar la sincronización con ella", + "sync-from-server": "Ya tengo una instancia de servidor y quiero configurar la sincronización con ella", + "next": "Siguiente", + "init-in-progress": "Inicialización del documento en curso", + "redirecting": "En breve será redirigido a la aplicación.", + "title": "Configuración" + }, + "setup_sync-from-desktop": { + "heading": "Sincronizar desde el escritorio", + "description": "Esta configuración debe iniciarse desde la instancia de escritorio:", + "step1": "Abra su instancia de escritorio de TriliumNext Notes.", + "step2": "En el menú Trilium, dé clic en Opciones.", + "step3": "Dé clic en la categoría Sincronizar.", + "step4": "Cambie la dirección de la instancia del servidor a: {{- host}} y dé clic en Guardar.", + "step5": "Dé clic en el botón \"Probar sincronización\" para verificar que la conexión fue exitosa.", + "step6": "Una vez que haya completado estos pasos, dé clic en {{- link}}.", + "step6-here": "aquí" + }, + "setup_sync-from-server": { + "heading": "Sincronización desde el servidor", + "instructions": "Por favor, ingrese la dirección y las credenciales del servidor Trilium a continuación. Esto descargará todo el documento de Trilium desde el servidor y configurará la sincronización. Dependiendo del tamaño del documento y de la velocidad de su conexión, esto puede tardar un poco.", + "server-host": "Dirección del servidor Trilium", + "server-host-placeholder": "https://:", + "proxy-server": "Servidor proxy (opcional)", + "proxy-server-placeholder": "https://:", + "note": "Nota:", + "proxy-instruction": "Si deja la configuración de proxy en blanco, se utilizará el proxy del sistema (aplica únicamente a la aplicación de escritorio)", + "password": "Contraseña", + "password-placeholder": "Contraseña", + "back": "Atrás", + "finish-setup": "Finalizar la configuración" + }, + "setup_sync-in-progress": { + "heading": "Sincronización en progreso", + "successful": "La sincronización se ha configurado correctamente. La sincronización inicial tardará algún tiempo en finalizar. Una vez hecho esto, será redirigido a la página de inicio de sesión.", + "outstanding-items": "Elementos de sincronización destacados:", + "outstanding-items-default": "N/A" + }, + "share_404": { + "title": "No encontrado", + "heading": "No encontrado" + }, + "share_page": { + "parent": "padre:", + "clipped-from": "Esta nota fue recortada originalmente de {{- url}}", + "child-notes": "Subnotas:", + "no-content": "Esta nota no tiene contenido." + }, + "weekdays": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo" + }, + "months": { + "january": "Enero", + "february": "Febrero", + "march": "Marzo", + "april": "Abril", + "may": "Mayo", + "june": "Junio", + "july": "Julio", + "august": "Agosto", + "september": "Septiembre", + "october": "Octubre", + "november": "Noviembre", + "december": "Diciembre" + } } From b02c4b54e5b3b51df2e7999b731376b693dedeeb Mon Sep 17 00:00:00 2001 From: hasecilu Date: Fri, 25 Oct 2024 10:51:24 -0600 Subject: [PATCH 38/60] i18n: Fix source strings --- src/public/translations/en/translation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 984867d07..df79dae41 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -640,7 +640,7 @@ "print_note": "Print note", "save_revision": "Save revision", "convert_into_attachment_failed": "Converting note '{{title}}' failed.", - "convert_into_attachment_successful": "Note '{{title}' has been converted to attachment.", + "convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.", "convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?" }, "onclick_button": { @@ -1218,7 +1218,7 @@ "password": { "heading": "Password", "alert_message": "Please take care to remember your new password. Password is used for logging into the web interface and to encrypt protected notes. If you forget your password, then all your protected notes are forever lost.", - "reset_link": "click here to reset it.", + "reset_link": "Click here to reset it.", "old_password": "Old password", "new_password": "New password", "new_password_confirmation": "New password confirmation", From 53822fd47f2d38e155f4f05f8d0b262b0e80b982 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 20:44:08 +0300 Subject: [PATCH 39/60] client: Remove redundant global --- src/public/app/services/glob.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/public/app/services/glob.js b/src/public/app/services/glob.js index e3603a9e6..997a2ac34 100644 --- a/src/public/app/services/glob.js +++ b/src/public/app/services/glob.js @@ -26,21 +26,6 @@ function setupGlobs() { // for CKEditor integration (button on block toolbar) window.glob.importMarkdownInline = async () => appContext.triggerCommand("importMarkdownInline"); - window.glob.SEARCH_HELP_TEXT = ` - Search tips - also see -

-

    -
  • Just enter any text for full text search
  • -
  • #abc - returns notes with label abc
  • -
  • #year = 2019 - matches notes with label year having value 2019
  • -
  • #rock #pop - matches notes which have both rock and pop labels
  • -
  • #rock or #pop - only one of the labels must be present
  • -
  • #year <= 2000 - numerical comparison (also >, >=, <).
  • -
  • note.dateCreated >= MONTH-1 - notes created in the last month
  • -
  • =handler - will execute script defined in handler relation to get results
  • -
-

`; - window.onerror = function (msg, url, lineNo, columnNo, error) { const string = msg.toLowerCase(); From 6799c44e22cbac6d9e727f3f77780ba0c19c3af4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 20:50:13 +0300 Subject: [PATCH 40/60] client: Fix redundant toast message --- src/public/app/widgets/right_panel_widget.js | 9 +-------- src/public/translations/cn/translation.json | 3 +-- src/public/translations/ro/translation.json | 1 - 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/public/app/widgets/right_panel_widget.js b/src/public/app/widgets/right_panel_widget.js index cb28aac43..019d5f4c2 100644 --- a/src/public/app/widgets/right_panel_widget.js +++ b/src/public/app/widgets/right_panel_widget.js @@ -57,14 +57,7 @@ class RightPanelWidget extends NoteContextAwareWidget { } this.initialized = this.doRenderBody().catch(e => { - toastService.showPersistent({ - title: t("toast.widget-error.title"), - icon: "alert", - message: t("toast.widget-error.message", { - title: this.widgetTitle, - message: e.message - }) - }); + this.logRenderingError(e); }); } diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index ecad19c86..054cb16e3 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -15,8 +15,7 @@ "message": "发生了严重错误,导致客户端应用程序无法启动:\n\n{{message}}\n\n这很可能是由于脚本以意外的方式失败引起的。请尝试以安全模式启动应用程序并解决问题。" }, "widget-error": { - "title": "小部件初始化失败", - "message": "标题为 \"{{title}}\" 的小部件由于以下原因无法初始化:\n\n{{message}}" + "title": "小部件初始化失败" } }, "add_link": { diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 51f09a9e1..50d638070 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1189,7 +1189,6 @@ "title": "Eroare critică" }, "widget-error": { - "message": "Widget-ul intitulat „{{title}}” nu a putut fi inițializat din cauza:\n\n{{message}}", "title": "Eroare la inițializarea unui widget", "message-custom": "Widget-ul personalizat din notița cu ID-ul „{{id}}”, întitulată ”{{title}}” nu a putut fi inițializată din cauza:\n\n{{message}}" } From 2324c9a13b9973f53ebf253eec8da325fb772788 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 20:51:50 +0300 Subject: [PATCH 41/60] client: Fix HTML in some toasts --- src/public/app/services/toast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/services/toast.js b/src/public/app/services/toast.js index 369a50e9d..31991a3c9 100644 --- a/src/public/app/services/toast.js +++ b/src/public/app/services/toast.js @@ -16,7 +16,7 @@ function toast(options) { ); $toast.find('.toast-title').text(options.title); - $toast.find('.toast-body').text(options.message); + $toast.find('.toast-body').html(options.message); if (options.id) { $toast.attr("id", `toast-${options.id}`); From 391f518c0144176b5759ca1436ee59c3e5bcdeef Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 21:04:13 +0300 Subject: [PATCH 42/60] i18n: Translate search note prefix --- src/public/app/widgets/search_options/search_string.js | 4 ++-- src/public/translations/en/translation.json | 3 ++- src/public/translations/ro/translation.json | 3 ++- src/services/special_notes.ts | 3 ++- translations/en/server.json | 3 +++ translations/ro/server.json | 3 +++ 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/public/app/widgets/search_options/search_string.js b/src/public/app/widgets/search_options/search_string.js index b430b691f..f71df76ac 100644 --- a/src/public/app/widgets/search_options/search_string.js +++ b/src/public/app/widgets/search_options/search_string.js @@ -61,9 +61,9 @@ export default class SearchString extends AbstractSearchOption { await this.setAttribute('label', 'searchString', searchString); - if (this.note.title.startsWith('Search: ')) { + if (this.note.title.startsWith(t("search_string.search_prefix"))) { await server.put(`notes/${this.note.noteId}/title`, { - title: `Search: ${searchString.length < 30 ? searchString : `${searchString.substr(0, 30)}…`}` + title: `${t("search_string.search_prefix")} ${searchString.length < 30 ? searchString : `${searchString.substr(0, 30)}…`}` }); } }, 1000); diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index df79dae41..06604c638 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -896,7 +896,8 @@ "label_rock_or_pop": "only one of the labels must be present", "label_year_comparison": "numerical comparison (also >, >=, <).", "label_date_created": "notes created in the last month", - "error": "Search error: {{error}}" + "error": "Search error: {{error}}", + "search_prefix": "Search:" }, "attachment_detail": { "open_help_page": "Open help page on attachments", diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 50d638070..ecc577ef2 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1094,7 +1094,8 @@ "label_year_comparison": "comparații numerice (de asemenea >, >=, <).", "placeholder": "cuvinte cheie pentru căutarea în conținut, #etichetă = valoare...", "search_syntax": "Sintaxa de căutare", - "title_column": "Textul de căutat:" + "title_column": "Textul de căutat:", + "search_prefix": "Căutare:" }, "shortcuts": { "action_name": "Denumirea acțiunii", diff --git a/src/services/special_notes.ts b/src/services/special_notes.ts index 50ba2eccf..991e7f3ea 100644 --- a/src/services/special_notes.ts +++ b/src/services/special_notes.ts @@ -8,6 +8,7 @@ import hoistedNoteService from "./hoisted_note.js"; import searchService from "./search/services/search.js"; import SearchContext from "./search/search_context.js"; import hiddenSubtree from "./hidden_subtree.js"; +import { t } from "i18next"; const { LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT } = hiddenSubtree; function getInboxNote(date: string) { @@ -75,7 +76,7 @@ function saveSqlConsole(sqlConsoleNoteId: string) { function createSearchNote(searchString: string, ancestorNoteId: string) { const {note} = noteService.createNewNote({ parentNoteId: getMonthlyParentNoteId('_search', 'search'), - title: `Search: ${searchString}`, + title: `${t("special_notes.search_prefix")} ${searchString}`, content: "", type: 'search', mime: 'application/json' diff --git a/translations/en/server.json b/translations/en/server.json index 68de4c6f0..069094f28 100644 --- a/translations/en/server.json +++ b/translations/en/server.json @@ -180,5 +180,8 @@ "october": "October", "november": "November", "december": "December" + }, + "special_notes": { + "search_prefix": "Search:" } } diff --git a/translations/ro/server.json b/translations/ro/server.json index 053823a57..5fd329a80 100644 --- a/translations/ro/server.json +++ b/translations/ro/server.json @@ -180,5 +180,8 @@ "october": "Octombrie", "november": "Noiembrie", "december": "Decembrie" + }, + "special_notes": { + "search_prefix": "Căutare:" } } From 84d216da543ed81c5d803856dd7d4c33a9266e4f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 25 Oct 2024 21:06:03 +0300 Subject: [PATCH 43/60] i18n: Translate missing keys for Romanian --- src/public/translations/ro/translation.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index ecc577ef2..db59fd359 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1191,7 +1191,12 @@ }, "widget-error": { "title": "Eroare la inițializarea unui widget", - "message-custom": "Widget-ul personalizat din notița cu ID-ul „{{id}}”, întitulată ”{{title}}” nu a putut fi inițializată din cauza:\n\n{{message}}" + "message-custom": "Widget-ul personalizat din notița cu ID-ul „{{id}}”, întitulată ”{{title}}” nu a putut fi inițializată din cauza:\n\n{{message}}", + "message-unknown": "Un widget necunoscut nu a putut fi inițializat din cauza:\n\n{{message}}" + }, + "bundle-error": { + "title": "Eroare la încărcarea unui script personalizat", + "message": "Scriptul din notița cu ID-ul „{{id}}”, întitulată „{{title}}” nu a putut fi executată din cauza:\n\n{{message}}" } }, "tray": { From 0768a2a0a30dc4ca178295b31947875ea10f540a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 26 Oct 2024 00:42:44 +0300 Subject: [PATCH 44/60] build: Add StartupWMClass to deb build --- bin/deb-options.json | 15 --------------- bin/electron-forge/desktop.ejs | 12 ++++++++++++ forge.config.cjs | 1 + 3 files changed, 13 insertions(+), 15 deletions(-) delete mode 100644 bin/deb-options.json create mode 100644 bin/electron-forge/desktop.ejs diff --git a/bin/deb-options.json b/bin/deb-options.json deleted file mode 100644 index 86531cc48..000000000 --- a/bin/deb-options.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "src": "dist/trilium-linux-x64", - "dest": "dist/", - "compression": "xz", - "name": "trilium", - "productName": "Trilium Notes", - "genericName": "Note taker", - "description": "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.", - "sections": "misc", - "maintainer": "zadam.apps@gmail.com", - "homepage": "https://github.com/zadam/trilium", - "bin": "trilium", - "icon": "dist/trilium-linux-x64/icon.png", - "categories": [ "Office" ] -} diff --git a/bin/electron-forge/desktop.ejs b/bin/electron-forge/desktop.ejs new file mode 100644 index 000000000..f803f37b2 --- /dev/null +++ b/bin/electron-forge/desktop.ejs @@ -0,0 +1,12 @@ +[Desktop Entry] +<% if (productName) { %>Name=<%= productName %> +<% } %><% if (description) { %>Comment=<%= description %> +<% } %><% if (genericName) { %>GenericName=<%= genericName %> +<% } %><% if (name) { %>Exec=<%= name %> %U +Icon=<%= name %> +<% } %>Type=Application +StartupNotify=true +<% if (productName) { %>StartupWMClass=<%= productName %> +<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>; +<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>; +<% } %> \ No newline at end of file diff --git a/forge.config.cjs b/forge.config.cjs index 91730ed43..2f0d64c4b 100644 --- a/forge.config.cjs +++ b/forge.config.cjs @@ -44,6 +44,7 @@ module.exports = { config: { options: { icon: "./images/app-icons/png/128x128.png", + desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs") } } }, From e3e6f56a88786ea11078b56ef98c9e40072fd5a0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 26 Oct 2024 00:58:02 +0300 Subject: [PATCH 45/60] build: Add icon.png for Linux builds (fixes #507) --- forge.config.cjs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/forge.config.cjs b/forge.config.cjs index 2f0d64c4b..e1c02a2f3 100644 --- a/forge.config.cjs +++ b/forge.config.cjs @@ -20,13 +20,20 @@ module.exports = { afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => { const extraResources = getExtraResourcesForPlatform(); for (const resource of extraResources) { + const baseName = path.basename(resource); let sourcePath; if (platform === 'darwin') { - sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', path.basename(resource)); + sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', baseName); } else { sourcePath = path.join(buildPath, 'resources', path.basename(resource)); } - const destPath = path.join(buildPath, path.basename(resource)); + let destPath; + + if (baseName !== "256x256.png") { + destPath = path.join(buildPath, path.basename(resource)); + } else { + destPath = path.join(buildPath, "icon.png"); + } // Copy files from resources folder to root fs.move(sourcePath, destPath) @@ -96,6 +103,7 @@ function getExtraResourcesForPlatform() { case 'darwin': break; case 'linux': + resources.push("images/app-icons/png/256x256.png") for (const script of scripts) { resources.push(`./bin/tpl/${script}.sh`) } From 88cd2ac25c8689b98621bd0f4d2692c2b36a87f5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 26 Oct 2024 01:00:44 +0300 Subject: [PATCH 46/60] build: Fix duplication --- forge.config.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge.config.cjs b/forge.config.cjs index e1c02a2f3..8d2db5da5 100644 --- a/forge.config.cjs +++ b/forge.config.cjs @@ -25,12 +25,12 @@ module.exports = { if (platform === 'darwin') { sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', baseName); } else { - sourcePath = path.join(buildPath, 'resources', path.basename(resource)); + sourcePath = path.join(buildPath, 'resources', baseName); } let destPath; if (baseName !== "256x256.png") { - destPath = path.join(buildPath, path.basename(resource)); + destPath = path.join(buildPath, baseName); } else { destPath = path.join(buildPath, "icon.png"); } From acf37f932711ff694e2fca5129da6172802aa0c9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 26 Oct 2024 23:39:38 +0300 Subject: [PATCH 47/60] client: Fix error when duplicating note --- src/public/app/services/note_create.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/app/services/note_create.js b/src/public/app/services/note_create.js index 995dec892..cc3bbb42f 100644 --- a/src/public/app/services/note_create.js +++ b/src/public/app/services/note_create.js @@ -5,6 +5,7 @@ import ws from "./ws.js"; import froca from "./froca.js"; import treeService from "./tree.js"; import toastService from "./toast.js"; +import { t } from "./i18n.js"; async function createNote(parentNotePath, options = {}) { options = Object.assign({ From 2387bbd17fcab34dfbc271223f0526663719d5ba Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Wed, 30 Oct 2024 22:30:40 +0800 Subject: [PATCH 48/60] Automatically trigger autocomplete on focus. --- src/public/app/widgets/type_widgets/empty.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/public/app/widgets/type_widgets/empty.js b/src/public/app/widgets/type_widgets/empty.js index fba2a1f7f..0a7119b80 100644 --- a/src/public/app/widgets/type_widgets/empty.js +++ b/src/public/app/widgets/type_widgets/empty.js @@ -84,6 +84,13 @@ export default class EmptyTypeWidget extends TypeWidget { ); } + // Automatically trigger autocomplete on focus. + this.$autoComplete.on('focus', () => { + // simulate pressing down arrow to trigger autocomplete + this.$autoComplete.trigger($.Event('keydown', { which: 40 })); // arrow down + this.$autoComplete.trigger($.Event('keydown', { which: 38 })); // arrow up + }); + this.$autoComplete .trigger('focus') .trigger('select'); From b10e2d9ec4e6ce189b392db8c6dd741d1f3a942e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 31 Oct 2024 14:00:14 +0200 Subject: [PATCH 49/60] Update README to add a few shields --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 22bb14c94..dc0e1f662 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # TriliumNext Notes +![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/notes) ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/triliumnext/notes/total) + [English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md) TriliumNext Notes is an open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases. From f8df3a6933914c13f4bcec2d82b81deb2a6af846 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 31 Oct 2024 17:48:33 +0200 Subject: [PATCH 50/60] client: Fix crash for some unhandled rejections --- src/public/app/services/glob.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/app/services/glob.js b/src/public/app/services/glob.js index 997a2ac34..f523a677e 100644 --- a/src/public/app/services/glob.js +++ b/src/public/app/services/glob.js @@ -50,11 +50,11 @@ function setupGlobs() { }; window.addEventListener("unhandledrejection", (e) => { - const string = e.reason.message.toLowerCase(); + const string = e?.reason?.message?.toLowerCase(); let message = "Uncaught error: "; - if (string.includes("script error")) { + if (string?.includes("script error")) { message += 'No details available'; } else { message += [ From 57a86c75d87a20e525ccd72693f24a993860736d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 31 Oct 2024 23:54:50 +0200 Subject: [PATCH 51/60] i18n: Fix single Romanian translation --- src/public/translations/ro/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index db59fd359..6efc98387 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -554,7 +554,7 @@ "open_sql_console": "Deschide consola SQL", "open_sql_console_history": "Deschide istoricul consolei SQL", "options": "Opțiuni", - "reload_frontend": "Reîncarcă interfață", + "reload_frontend": "Reîncarcă interfața", "reload_hint": "Reîncărcarea poate ajuta atunci când există ceva probleme vizuale fără a trebui repornită întreaga aplicație.", "reset_zoom_level": "Resetează nivelul de zoom", "show_backend_log": "Afișează log-ul din backend", From dbca50d9b0d74650c94481b86ff2c514e08d6fe7 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 1 Nov 2024 14:45:49 +0800 Subject: [PATCH 52/60] Make note-detail-empty always display autocompletion. --- src/public/app/widgets/type_widgets/empty.js | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/public/app/widgets/type_widgets/empty.js b/src/public/app/widgets/type_widgets/empty.js index 0a7119b80..45da73e6e 100644 --- a/src/public/app/widgets/type_widgets/empty.js +++ b/src/public/app/widgets/type_widgets/empty.js @@ -18,10 +18,9 @@ const TPL = ` width: 130px; text-align: center; margin: 10px; - padding; 10px; border: 1px transparent solid; } - + .workspace-notes .workspace-note:hover { cursor: pointer; border: 1px solid var(--main-border-color); @@ -33,14 +32,14 @@ const TPL = ` } +
-
+
- -
+
`; export default class EmptyTypeWidget extends TypeWidget { @@ -51,10 +50,12 @@ export default class EmptyTypeWidget extends TypeWidget { this.$widget = $(TPL); this.$autoComplete = this.$widget.find(".note-autocomplete"); + this.$results = this.$widget.find(".note-detail-empty-results"); noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, { hideGoToSelectedNoteButton: true, - allowCreatingNotes: true + allowCreatingNotes: true, + container: this.$results }) .on('autocomplete:noteselected', function(event, suggestion, dataset) { if (!suggestion.notePath) { @@ -84,15 +85,10 @@ export default class EmptyTypeWidget extends TypeWidget { ); } - // Automatically trigger autocomplete on focus. - this.$autoComplete.on('focus', () => { - // simulate pressing down arrow to trigger autocomplete - this.$autoComplete.trigger($.Event('keydown', { which: 40 })); // arrow down - this.$autoComplete.trigger($.Event('keydown', { which: 38 })); // arrow up - }); - this.$autoComplete .trigger('focus') .trigger('select'); + + noteAutocompleteService.showRecentNotes(this.$autoComplete); } } From cdd5a17fcede0428572b4b2f4f17666ccc12ebd4 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 1 Nov 2024 15:30:31 +0800 Subject: [PATCH 53/60] Make note-detail-empty always display autocompletion. --- src/public/app/widgets/type_widgets/empty.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/public/app/widgets/type_widgets/empty.js b/src/public/app/widgets/type_widgets/empty.js index 45da73e6e..7576d09df 100644 --- a/src/public/app/widgets/type_widgets/empty.js +++ b/src/public/app/widgets/type_widgets/empty.js @@ -67,6 +67,7 @@ export default class EmptyTypeWidget extends TypeWidget { this.$workspaceNotes = this.$widget.find('.workspace-notes'); + noteAutocompleteService.showRecentNotes(this.$autoComplete); super.doRender(); } @@ -88,7 +89,5 @@ export default class EmptyTypeWidget extends TypeWidget { this.$autoComplete .trigger('focus') .trigger('select'); - - noteAutocompleteService.showRecentNotes(this.$autoComplete); } } From 7f17f93767ea646837026c43ff29f831e2b29279 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 1 Nov 2024 21:43:09 +0800 Subject: [PATCH 54/60] Crop fileName and prevent cutting into the extension. --- src/services/export/zip.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/services/export/zip.ts b/src/services/export/zip.ts index c98c226c8..5e30e6297 100644 --- a/src/services/export/zip.ts +++ b/src/services/export/zip.ts @@ -58,8 +58,15 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record): string { let fileName = baseFileName.trim(); + + // Crop fileName to avoid its length exceeding 30 and prevent cutting into the extension. if (fileName.length > 30) { - fileName = fileName.substr(0, 30).trim(); + let match = fileName.match(/(\.[a-zA-Z0-9_.!#-]+)$/); + let ext = match ? match[0] : ''; + // Crop the extension if extension length exceeds 30 + const croppedExt = ext.slice(-30); + // Crop the file name section and append the cropped extension + fileName = fileName.slice(0, 30 - croppedExt.length) + croppedExt; } let existingExtension = path.extname(fileName).toLowerCase(); @@ -76,6 +83,9 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h else if (mime === 'application/x-javascript' || mime === 'text/javascript') { newExtension = 'js'; } + else if (type === 'canvas' || mime === 'application/json') { + newExtension = 'json'; + } else if (existingExtension.length > 0) { // if the page already has an extension, then we'll just keep it newExtension = null; } From 7b24f7e3325ec3789a4a69577f366040411df879 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Fri, 1 Nov 2024 20:02:22 +0800 Subject: [PATCH 55/60] close right tabs --- src/public/app/components/tab_manager.js | 14 +++++++++++++- src/public/app/widgets/tab_row.js | 3 ++- src/public/translations/en/translation.json | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/public/app/components/tab_manager.js b/src/public/app/components/tab_manager.js index 861a54502..235aa5cd1 100644 --- a/src/public/app/components/tab_manager.js +++ b/src/public/app/components/tab_manager.js @@ -551,7 +551,7 @@ export default class TabManager extends Component { await this.removeNoteContext(ntxIdToRemove); } } - + async closeOtherTabsCommand({ntxId}) { for (const ntxIdToRemove of this.mainNoteContexts.map(nc => nc.ntxId)) { if (ntxIdToRemove !== ntxId) { @@ -560,6 +560,18 @@ export default class TabManager extends Component { } } + async closeRightTabsCommand({ntxId}) { + const ntxIds = this.mainNoteContexts.map(nc => nc.ntxId); + const index = ntxIds.indexOf(ntxId); + + if (index !== -1) { + const idsToRemove = ntxIds.slice(index + 1); + for (const ntxIdToRemove of idsToRemove) { + await this.removeNoteContext(ntxIdToRemove); + } + } + } + async closeTabCommand({ntxId}) { await this.removeNoteContext(ntxId); } diff --git a/src/public/app/widgets/tab_row.js b/src/public/app/widgets/tab_row.js index 395dd51d5..cc6f8a22b 100644 --- a/src/public/app/widgets/tab_row.js +++ b/src/public/app/widgets/tab_row.js @@ -260,7 +260,8 @@ export default class TabRowWidget extends BasicWidget { y: e.pageY, items: [ {title: t('tab_row.close'), command: "closeTab", uiIcon: "bx bx-x"}, - {title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-x"}, + {title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-x", enabled: appContext.tabManager.noteContexts.length !== 1}, + {title: t('tab_row.close_right_tabs'), command: "closeRightTabs", uiIcon: "bx bx-x", enabled: appContext.tabManager.noteContexts.at(-1).ntxId !== ntxId}, {title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-x"}, {title: t('tab_row.move_tab_to_new_window'), command: "moveTabToNewWindow", uiIcon: "bx bx-window-open"} ], diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 06604c638..dd170d30a 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1434,6 +1434,7 @@ "add_new_tab": "Add new tab", "close": "Close", "close_other_tabs": "Close other tabs", + "close_right_tabs": "Close right tabs", "close_all_tabs": "Close all tabs", "move_tab_to_new_window": "Move this tab to a new window", "new_tab": "New tab" From 11a82e62f1836e055f6ce07c9f32f15c5c07c5cf Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 1 Nov 2024 19:03:06 +0200 Subject: [PATCH 56/60] client: Change layout of tab context menu slightly --- src/public/app/widgets/tab_row.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/tab_row.js b/src/public/app/widgets/tab_row.js index cc6f8a22b..ccf49569b 100644 --- a/src/public/app/widgets/tab_row.js +++ b/src/public/app/widgets/tab_row.js @@ -260,9 +260,10 @@ export default class TabRowWidget extends BasicWidget { y: e.pageY, items: [ {title: t('tab_row.close'), command: "closeTab", uiIcon: "bx bx-x"}, - {title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-x", enabled: appContext.tabManager.noteContexts.length !== 1}, - {title: t('tab_row.close_right_tabs'), command: "closeRightTabs", uiIcon: "bx bx-x", enabled: appContext.tabManager.noteContexts.at(-1).ntxId !== ntxId}, - {title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-x"}, + {title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.length !== 1}, + {title: t('tab_row.close_right_tabs'), command: "closeRightTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.at(-1).ntxId !== ntxId}, + {title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-empty"}, + { title: "----" }, {title: t('tab_row.move_tab_to_new_window'), command: "moveTabToNewWindow", uiIcon: "bx bx-window-open"} ], selectMenuItemHandler: ({command}) => { From d0904c1051e40eae96dde4a8c5eb36ac343252af Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 1 Nov 2024 19:05:56 +0200 Subject: [PATCH 57/60] client: Change translation for closing tabs to the right --- src/public/translations/en/translation.json | 2 +- src/public/translations/ro/translation.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index dd170d30a..9de64b6bf 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1434,7 +1434,7 @@ "add_new_tab": "Add new tab", "close": "Close", "close_other_tabs": "Close other tabs", - "close_right_tabs": "Close right tabs", + "close_right_tabs": "Close tabs to the right", "close_all_tabs": "Close all tabs", "move_tab_to_new_window": "Move this tab to a new window", "new_tab": "New tab" diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 6efc98387..b5bff39a8 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -1439,7 +1439,8 @@ "close_other_tabs": "Închide celelalte taburi", "close_tab": "Închide tab", "move_tab_to_new_window": "Mută acest tab în altă fereastră", - "new_tab": "Tab nou" + "new_tab": "Tab nou", + "close_right_tabs": "Închide taburile din dreapta" }, "toc": { "options": "Setări", From c2baa4b752e4ce7189f081a8cbe0a832bb1f880d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 1 Nov 2024 19:43:39 +0200 Subject: [PATCH 58/60] server: Add comment to clarify use of regex --- src/services/export/zip.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/export/zip.ts b/src/services/export/zip.ts index 5e30e6297..75feeebc6 100644 --- a/src/services/export/zip.ts +++ b/src/services/export/zip.ts @@ -61,6 +61,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h // Crop fileName to avoid its length exceeding 30 and prevent cutting into the extension. if (fileName.length > 30) { + // We use regex to match the extension to preserve multiple dots in extensions (e.g. .tar.gz). let match = fileName.match(/(\.[a-zA-Z0-9_.!#-]+)$/); let ext = match ? match[0] : ''; // Crop the extension if extension length exceeds 30 From aa7d7b3afd3f3f5873a28eb28f2154d6efc7e41c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 1 Nov 2024 20:20:53 +0200 Subject: [PATCH 59/60] client: Add borders to empty tab search list --- src/public/app/widgets/type_widgets/empty.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/type_widgets/empty.js b/src/public/app/widgets/type_widgets/empty.js index 7576d09df..8d04ab5b9 100644 --- a/src/public/app/widgets/type_widgets/empty.js +++ b/src/public/app/widgets/type_widgets/empty.js @@ -25,6 +25,21 @@ const TPL = ` cursor: pointer; border: 1px solid var(--main-border-color); } + + .note-detail-empty-results { + max-height: 50vh; + overflow: scroll; + border: var(--bs-border-width) solid var(--bs-border-color); + border-top: 0; + } + + .empty-tab-search .note-autocomplete-input { + border-bottom-left-radius: 0; + } + + .empty-tab-search .input-clearer-button { + border-bottom-right-radius: 0; + } .workspace-icon { text-align: center; @@ -33,7 +48,7 @@ const TPL = `
-
+