From 5da0053f6a47a28d13848936047f109a250cdce2 Mon Sep 17 00:00:00 2001 From: iamvann Date: Fri, 16 May 2025 22:42:08 +0800 Subject: [PATCH 1/4] options (setting) feature: add UI for cutom date time format (Alt+t) under options/Text Notes --- .../widgets/type_widgets/content_widget.js | 2 + .../options/text_notes/date_time_format.js | 74 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js diff --git a/src/public/app/widgets/type_widgets/content_widget.js b/src/public/app/widgets/type_widgets/content_widget.js index fe7f105af..aaa33470f 100644 --- a/src/public/app/widgets/type_widgets/content_widget.js +++ b/src/public/app/widgets/type_widgets/content_widget.js @@ -8,6 +8,7 @@ import KeyboardShortcutsOptions from "./options/shortcuts.js"; import HeadingStyleOptions from "./options/text_notes/heading_style.js"; import TableOfContentsOptions from "./options/text_notes/table_of_contents.js"; import HighlightsListOptions from "./options/text_notes/highlights_list.js"; +import DateTimeFormatOptions from "./options/text_notes/date_time_format.js"; import TextAutoReadOnlySizeOptions from "./options/text_notes/text_auto_read_only_size.js"; import VimKeyBindingsOptions from "./options/code_notes/vim_key_bindings.js"; import WrapLinesOptions from "./options/code_notes/wrap_lines.js"; @@ -66,6 +67,7 @@ const CONTENT_WIDGETS = { HeadingStyleOptions, TableOfContentsOptions, HighlightsListOptions, + DateTimeFormatOptions, TextAutoReadOnlySizeOptions ], _optionsCodeNotes: [ diff --git a/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js b/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js new file mode 100644 index 000000000..5bf427778 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js @@ -0,0 +1,74 @@ +import OptionsWidget from "../options_widget.js"; + +const TPL = ` +
+

Custom Date/Time Format (Alt+T)

+ +

+ Define a custom format for the date and time inserted using the Alt+T shortcut. + Uses Day.js format tokens. + Refer to the Day.js documentation for valid tokens. +

+

+ Important: If you provide a string that Day.js cannot interpret as a format, + the literal string you typed might be inserted. If the format string is empty, or if Day.js + encounters an internal error with your format, a default format (e.g., YYYY-MM-DD HH:mm) will be used. +

+ +
+ + +
+

+ Examples of valid Day.js formats: + YYYY-MM-DD HH:mm (Default-like), + DD.MM.YYYY, + MMMM D, YYYY h:mm A, + [Today is] dddd +

+
+`; + +export default class DateTimeFormatOptions extends OptionsWidget { + doRender() { + this.$widget = $(TPL); + this.$formatInput = this.$widget.find( + "input.custom-datetime-format-input" + ); + + //listen to input + this.$formatInput.on("input", () => { + const formatString = this.$formatInput.val(); + + this.updateOption("customDateTimeFormatString", formatString); + }); + + return this.$widget; //render method to return the widget + } + + async optionsLoaded(options) { + //todo: update the key in updateOption + const currentFormat = options.customDateTimeFormatString || ""; + + + if (this.$formatInput) { + this.$formatInput.val(currentFormat); + } else { + + console.warn( + "DateTimeFormatOptions: $formatInput not initialized when optionsLoaded was called. Attempting to find again." + ); + const inputField = this.$widget.find( + "input.custom-datetime-format-input" + ); + if (inputField.length) { + this.$formatInput = inputField; + this.$formatInput.val(currentFormat); + } else { + console.error( + "DateTimeFormatOptions: Could not find format input field in optionsLoaded." + ); + } + } + } +} From d26e8758ca339fa1740922f61d45af89d90a71b1 Mon Sep 17 00:00:00 2001 From: iamvann Date: Fri, 16 May 2025 23:20:02 +0800 Subject: [PATCH 2/4] implement custom date/time formatting for Alt + T --- src/public/app/services/utils.js | 28 +++++++++- .../app/widgets/type_widgets/editable_text.js | 55 +++++++++++++------ .../options/text_notes/date_time_format.js | 1 - src/routes/api/options.js | 3 +- 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index b88146012..59e7f22c4 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -73,8 +73,32 @@ function formatDateISO(date) { return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`; } -function formatDateTime(date) { - return `${formatDate(date)} ${formatTime(date)}`; +// old version +// function formatDateTime(date) { +// return `${formatDate(date)} ${formatTime(date)}`; +// } + +// In utils.js +// import dayjs from 'dayjs'; // Assuming dayjs is available in this scope + +function formatDateTime(date, userSuppliedFormat) { + const DEFAULT_FORMAT = 'YYYY-MM-DD HH:mm'; + let formatToUse = DEFAULT_FORMAT; + + if (userSuppliedFormat && typeof userSuppliedFormat === 'string' && userSuppliedFormat.trim() !== "") { + formatToUse = userSuppliedFormat.trim(); + } + + if (!date) { + date = new Date(); + } + + try { + return dayjs(date).format(formatToUse); + } catch (e) { + console.warn(`Trilium: Day.js encountered an error with format string "${formatToUse}". Falling back to default. Error: ${e.message}`); + return dayjs(date).format(DEFAULT_FORMAT); + } } function localNowDateTime() { diff --git a/src/public/app/widgets/type_widgets/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index 48cb041aa..1867a58d9 100644 --- a/src/public/app/widgets/type_widgets/editable_text.js +++ b/src/public/app/widgets/type_widgets/editable_text.js @@ -109,9 +109,9 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { (await mimeTypesService.getMimeTypes()) .filter(mt => mt.enabled) .map(mt => ({ - language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"), - label: mt.title - })); + language: mt.mime.toLowerCase().replace(/[\W_]+/g, "-"), + label: mt.title + })); // CKEditor since version 12 needs the element to be visible before initialization. At the same time, // we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate @@ -211,7 +211,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { this.watchdog?.editor.editing.view.focus(); } - show() {} + show() { } getEditor() { return this.watchdog?.editor; @@ -225,10 +225,29 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { } } - insertDateTimeToTextCommand() { - const date = new Date(); - const dateString = utils.formatDateTime(date); + // old version + // insertDateTimeToTextCommand() { + // const date = new Date(); + // const dateString = utils.formatDateTime(date); + // this.addTextToEditor(dateString); + // } + + // new version + async insertDateTimeToTextCommand() { + const date = new Date(); + let userPreferredFormat = ""; //Default + + try { + const allOptions = await server.get('options'); + + if (allOptions && typeof allOptions.customDateTimeFormatString === 'string') { + userPreferredFormat = allOptions.customDateTimeFormatString; + } + } catch (e) { + console.error("Trilium: Failed to fetch options for custom date/time format. Using default.", e); + } + const dateString = utils.formatDateTime(date, userPreferredFormat); this.addTextToEditor(dateString); } @@ -237,7 +256,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { this.watchdog.editor.model.change(writer => { const insertPosition = this.watchdog.editor.model.document.selection.getFirstPosition(); - writer.insertText(linkTitle, {linkHref: linkHref}, insertPosition); + writer.insertText(linkTitle, { linkHref: linkHref }, insertPosition); }); } @@ -250,7 +269,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { }); } - addTextToActiveEditorEvent({text}) { + addTextToActiveEditorEvent({ text }) { if (!this.isActive()) { return; } @@ -283,7 +302,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { return !selection.isCollapsed; } - async executeWithTextEditorEvent({callback, resolve, ntxId}) { + async executeWithTextEditorEvent({ callback, resolve, ntxId }) { if (!this.isNoteContext(ntxId)) { return; } @@ -300,7 +319,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { addLinkToTextCommand() { const selectedText = this.getSelectedText(); - this.triggerCommand('showAddLinkDialog', {textTypeWidget: this, text: selectedText}) + this.triggerCommand('showAddLinkDialog', { textTypeWidget: this, text: selectedText }) } getSelectedText() { @@ -347,29 +366,29 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { } addIncludeNoteToTextCommand() { - this.triggerCommand("showIncludeNoteDialog", {textTypeWidget: this}); + this.triggerCommand("showIncludeNoteDialog", { textTypeWidget: this }); } addIncludeNote(noteId, boxSize) { - this.watchdog.editor.model.change( writer => { + this.watchdog.editor.model.change(writer => { // Insert * at the current selection position // in a way that will result in creating a valid model structure this.watchdog.editor.model.insertContent(writer.createElement('includeNote', { noteId: noteId, boxSize: boxSize })); - } ); + }); } async addImage(noteId) { const note = await froca.getNote(noteId); - this.watchdog.editor.model.change( writer => { + this.watchdog.editor.model.change(writer => { const encodedTitle = encodeURIComponent(note.title); const src = `api/images/${note.noteId}/${encodedTitle}`; - this.watchdog.editor.execute( 'insertImage', { source: src } ); - } ); + this.watchdog.editor.execute('insertImage', { source: src }); + }); } async createNoteForReferenceLink(title) { @@ -385,7 +404,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { return resp.note.getBestNotePathString(); } - async refreshIncludedNoteEvent({noteId}) { + async refreshIncludedNoteEvent({ noteId }) { this.refreshIncludedNote(this.$editor, noteId); } } diff --git a/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js b/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js index 5bf427778..96d70665e 100644 --- a/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js +++ b/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js @@ -47,7 +47,6 @@ export default class DateTimeFormatOptions extends OptionsWidget { } async optionsLoaded(options) { - //todo: update the key in updateOption const currentFormat = options.customDateTimeFormatString || ""; diff --git a/src/routes/api/options.js b/src/routes/api/options.js index efd70a07e..5f317945b 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -57,7 +57,8 @@ const ALLOWED_OPTIONS = new Set([ 'customSearchEngineName', 'customSearchEngineUrl', 'promotedAttributesOpenInRibbon', - 'editedNotesOpenInRibbon' + 'editedNotesOpenInRibbon', + 'customDateTimeFormatString' ]); function getOptions() { From 26f6c28c71553725221b89f3fabb890ccc536c64 Mon Sep 17 00:00:00 2001 From: iamvann Date: Fri, 16 May 2025 23:27:25 +0800 Subject: [PATCH 3/4] fix: add import server to editable_text.js --- src/public/app/services/utils.js | 12 +++++++----- src/public/app/widgets/type_widgets/editable_text.js | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index 59e7f22c4..99871035a 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -81,23 +81,25 @@ function formatDateISO(date) { // In utils.js // import dayjs from 'dayjs'; // Assuming dayjs is available in this scope +// new version function formatDateTime(date, userSuppliedFormat) { - const DEFAULT_FORMAT = 'YYYY-MM-DD HH:mm'; - let formatToUse = DEFAULT_FORMAT; + let formatToUse; if (userSuppliedFormat && typeof userSuppliedFormat === 'string' && userSuppliedFormat.trim() !== "") { formatToUse = userSuppliedFormat.trim(); + } else { + formatToUse = 'YYYY-MM-DD HH:mm'; // Trilium's default format } if (!date) { - date = new Date(); + date = new Date(); } try { return dayjs(date).format(formatToUse); } catch (e) { - console.warn(`Trilium: Day.js encountered an error with format string "${formatToUse}". Falling back to default. Error: ${e.message}`); - return dayjs(date).format(DEFAULT_FORMAT); + console.warn(`Day.js: Invalid format string "${formatToUse}". Falling back. Error:`, e.message); + return dayjs(date).format('YYYY-MM-DD HH:mm'); } } diff --git a/src/public/app/widgets/type_widgets/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index 1867a58d9..897add401 100644 --- a/src/public/app/widgets/type_widgets/editable_text.js +++ b/src/public/app/widgets/type_widgets/editable_text.js @@ -9,6 +9,7 @@ import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; import link from "../../services/link.js"; import appContext from "../../components/app_context.js"; import dialogService from "../../services/dialog.js"; +import server from '../../services/server.js'; const ENABLE_INSPECTOR = false; From 2844331f1043137d57e0ada0441e89689900dbbc Mon Sep 17 00:00:00 2001 From: iamvann Date: Sat, 17 May 2025 01:26:26 +0800 Subject: [PATCH 4/4] docs(options):Ensures UI descriptions accurately reflect Day.js handling of unrecognized format --- .../options/text_notes/date_time_format.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js b/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js index 96d70665e..7eb5916c3 100644 --- a/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js +++ b/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js @@ -9,11 +9,13 @@ const TPL = ` Uses Day.js format tokens. Refer to the Day.js documentation for valid tokens.

-

- Important: If you provide a string that Day.js cannot interpret as a format, - the literal string you typed might be inserted. If the format string is empty, or if Day.js - encounters an internal error with your format, a default format (e.g., YYYY-MM-DD HH:mm) will be used. -

+

+ Important: If you provide a format string that Day.js does not recognize + (e.g., mostly plain text without valid Day.js tokens), + the text you typed might be inserted literally. If the format string is left empty, + or if Day.js encounters a critical internal error with your format, + a default format (e.g., YYYY-MM-DD HH:mm) will be used. +

@@ -39,8 +41,8 @@ export default class DateTimeFormatOptions extends OptionsWidget { //listen to input this.$formatInput.on("input", () => { const formatString = this.$formatInput.val(); - - this.updateOption("customDateTimeFormatString", formatString); + + this.updateOption("customDateTimeFormatString", formatString); }); return this.$widget; //render method to return the widget @@ -49,7 +51,7 @@ export default class DateTimeFormatOptions extends OptionsWidget { async optionsLoaded(options) { const currentFormat = options.customDateTimeFormatString || ""; - + if (this.$formatInput) { this.$formatInput.val(currentFormat); } else {