diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index b88146012..99871035a 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -73,8 +73,34 @@ 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 + +// new version +function formatDateTime(date, userSuppliedFormat) { + 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(); + } + + try { + return dayjs(date).format(formatToUse); + } catch (e) { + console.warn(`Day.js: Invalid format string "${formatToUse}". Falling back. Error:`, e.message); + return dayjs(date).format('YYYY-MM-DD HH:mm'); + } } function localNowDateTime() { 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/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index 48cb041aa..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; @@ -109,9 +110,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 +212,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { this.watchdog?.editor.editing.view.focus(); } - show() {} + show() { } getEditor() { return this.watchdog?.editor; @@ -225,10 +226,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 +257,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 +270,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { }); } - addTextToActiveEditorEvent({text}) { + addTextToActiveEditorEvent({ text }) { if (!this.isActive()) { return; } @@ -283,7 +303,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 +320,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 +367,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 +405,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 new file mode 100644 index 000000000..7eb5916c3 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/text_notes/date_time_format.js @@ -0,0 +1,75 @@ +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 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. +

+ +
+ + +
+

+ 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) { + 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." + ); + } + } + } +} 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() {