diff --git a/package-lock.json b/package-lock.json index 0890e5a..2c9443d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "3.1.0", "license": "MIT", "dependencies": { + "flatpickr": "^4.6.13", "front-matter": "^4.0.2", "handlebars": "^4.7.7", "html-entities": "^2.3.2", @@ -65,6 +66,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3916,6 +3918,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flatpickr": { + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", + "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==", + "license": "MIT" + }, "node_modules/flatted": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", @@ -8597,6 +8605,7 @@ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/package.json b/package.json index caca106..46db854 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "yargs": "^16.2.0" }, "dependencies": { + "flatpickr": "^4.6.13", "front-matter": "^4.0.2", "handlebars": "^4.7.7", "html-entities": "^2.3.2", diff --git a/src/parser.ts b/src/parser.ts index 36bd554..405a7ba 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -94,7 +94,7 @@ export class Parser { variableObjects[variableName] = getVariableFromDefinition(variableName, variables[variableName]); }); - await setTemplateVariablesView(this.dialog, title, variableObjects); + await setTemplateVariablesView(this.dialog, title, variableObjects, this.utils); const dialogResponse = (await joplin.views.dialogs.open(this.dialog)); if (dialogResponse.id === "cancel") { diff --git a/src/variables/types/base.ts b/src/variables/types/base.ts index b88a212..b61a19e 100644 --- a/src/variables/types/base.ts +++ b/src/variables/types/base.ts @@ -13,11 +13,11 @@ export class CustomVariable { this.label = label; } - protected inputHTML(): string { + protected inputHTML(dateAndTimeUtils?: DateAndTimeUtils): string { return ""; } - public toHTML(): string { + public toHTML(dateAndTimeUtils?: DateAndTimeUtils): string { return ( `
@@ -25,7 +25,7 @@ export class CustomVariable { ${encode(this.label)}
- ${this.inputHTML()} + ${this.inputHTML(dateAndTimeUtils)}
` diff --git a/src/variables/types/date.ts b/src/variables/types/date.ts index 3dd5241..51b90f6 100644 --- a/src/variables/types/date.ts +++ b/src/variables/types/date.ts @@ -1,16 +1,40 @@ import { DateAndTimeUtils } from "@templates/utils/dateAndTime"; import { encode } from "html-entities"; +import * as moment from "moment"; import { CustomVariable } from "./base"; export class DateCustomVariable extends CustomVariable { static definitionName = "date"; public processInput(input: string, dateAndTimeUtils: DateAndTimeUtils): string { - const inputDate = new Date(input); - return dateAndTimeUtils.formatMsToLocal(inputDate.getTime(), dateAndTimeUtils.getDateFormat()); + if (!input || input.trim() === "") { + return ""; + } + + const dateFormat = dateAndTimeUtils.getDateFormat(); + + try { + return dateAndTimeUtils.formatMsToLocal( + dateAndTimeUtils.formatLocalToJoplinCompatibleUnixTime(input, dateFormat), + dateFormat + ); + } catch { + // If the user typed an ISO date (e.g. YYYY-MM-DD), try parsing as ISO + const isoParsed = moment(input, moment.ISO_8601, true); + if (isoParsed.isValid()) { + return dateAndTimeUtils.formatMsToLocal(isoParsed.valueOf(), dateFormat); + } + + // Fall back to raw input rather than throwing, preventing the template dialog from crashing + return input; + } } - protected inputHTML(): string { - return ``; + protected inputHTML(dateAndTimeUtils?: DateAndTimeUtils): string { + const dateFormat = dateAndTimeUtils ? dateAndTimeUtils.getDateFormat() : "YYYY-MM-DD"; + // Use type="text" so flatpickr (loaded in the dialog webview) can + // attach its custom calendar UI. The data-datepicker-format attribute + // tells datepicker.js which Joplin format to use (fixes issue #112). + return ``; } } diff --git a/src/views/datepicker.js b/src/views/datepicker.js new file mode 100644 index 0000000..1b1ca2f --- /dev/null +++ b/src/views/datepicker.js @@ -0,0 +1,88 @@ +// datepicker.js — initializes flatpickr on all date inputs in the template dialog. +// Runs inside Joplin's dialog webview (Electron/Chromium). + +(function () { + /** + * Converts a Joplin/moment.js date format string to a flatpickr format string. + * Only covers the date formats supported by Joplin's settings. + */ + function momentToFlatpickr(momentFormat) { + return momentFormat + .replace(/YYYY/g, 'Y') // 4-digit year + .replace(/YY/g, 'y') // 2-digit year + .replace(/MM/g, 'm') // 2-digit month + .replace(/M/g, 'n') // 1-digit month (no leading zero) + .replace(/DD/g, 'd') // 2-digit day + .replace(/D/g, 'j'); // 1-digit day (no leading zero) + } + + /** + * Replaces the flatpickr year with a