Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"yargs": "^16.2.0"
},
"dependencies": {
"flatpickr": "^4.6.13",
"front-matter": "^4.0.2",
"handlebars": "^4.7.7",
"html-entities": "^2.3.2",
Expand Down
9 changes: 9 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Note } from "./utils/templates";
import { notEmpty } from "./utils/typescript";
import { getVariableFromDefinition } from "./variables/parser";
import { CustomVariable } from "./variables/types/base";
import { DateCustomVariable } from "./variables/types/date";
import { setTemplateVariablesView } from "./views/templateVariables";
import { HelperFactory } from "./helpers";

Expand Down Expand Up @@ -90,6 +91,14 @@ export class Parser {
variableObjects[variableName] = getVariableFromDefinition(variableName, variables[variableName]);
});

// Inject the user's Joplin date format into each DateCustomVariable
// so the placeholder matches their configured format (fixes issue #112).
for (const variable of Object.values(variableObjects)) {
if (variable instanceof DateCustomVariable) {
variable.setDateFormat(this.utils.getDateFormat());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why can't this be done inside "src/variables/types/date.ts"?

}
}

await setTemplateVariablesView(this.dialog, title, variableObjects);
const dialogResponse = (await joplin.views.dialogs.open(this.dialog));

Expand Down
24 changes: 21 additions & 3 deletions src/variables/types/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,30 @@ import { CustomVariable } from "./base";
export class DateCustomVariable extends CustomVariable {
static definitionName = "date";

// The Joplin date format (e.g. "YYYY-MM-DD"), injected by parser.ts before rendering.
private dateFormat = "YYYY-MM-DD";

/**
* Called by parser.ts before the dialog is rendered so the flatpickr
* date picker can use the user's configured Joplin date format.
*/
public setDateFormat(format: string): void {
this.dateFormat = format;
}

public processInput(input: string, dateAndTimeUtils: DateAndTimeUtils): string {
const inputDate = new Date(input);
return dateAndTimeUtils.formatMsToLocal(inputDate.getTime(), dateAndTimeUtils.getDateFormat());
// Parse the typed/picked date using Joplin's date format, then
// re-format it consistently with the same format.
return dateAndTimeUtils.formatMsToLocal(
dateAndTimeUtils.formatLocalToJoplinCompatibleUnixTime(input, dateAndTimeUtils.getDateFormat()),
dateAndTimeUtils.getDateFormat()
);
}

protected inputHTML(): string {
return `<input name="${encode(this.name)}" type="date"></input>`;
// 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 `<input name="${encode(this.name)}" type="text" data-datepicker-format="${encode(this.dateFormat)}" placeholder="${encode(this.dateFormat)}" autocomplete="off"></input>`;
}
}
88 changes: 88 additions & 0 deletions src/views/datepicker.js
Original file line number Diff line number Diff line change
@@ -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 <input> with a <select> dropdown so the
* user can jump to any year without having to click the tiny arrows.
* Range: 10 years before and 10 years after the current year.
*/
function addYearDropdown(fp) {
var yearInput = fp.calendarContainer.querySelector('input.cur-year');
if (!yearInput) return;

var currentYear = new Date().getFullYear();
var startYear = currentYear - 10;
var endYear = currentYear + 10;

var select = document.createElement('select');
select.className = 'year-select';

for (var y = startYear; y <= endYear; y++) {
var option = document.createElement('option');
option.value = y;
option.textContent = y;
if (y === parseInt(yearInput.value, 10)) {
option.selected = true;
}
select.appendChild(option);
}

// When user picks a year, jump flatpickr to that year.
select.addEventListener('change', function () {
fp.changeYear(parseInt(select.value, 10));
});

// Keep the dropdown in sync when flatpickr navigates months.
fp.config.onMonthChange.push(function () {
select.value = fp.currentYear;
});
fp.config.onYearChange.push(function () {
select.value = fp.currentYear;
});

// Replace the number input (and its wrapper) with our dropdown.
var wrapper = yearInput.closest('.numInputWrapper') || yearInput.parentNode;
wrapper.parentNode.replaceChild(select, wrapper);
}

function initDatePickers() {
var inputs = document.querySelectorAll('input[data-datepicker-format]');
inputs.forEach(function (input) {
var momentFormat = input.getAttribute('data-datepicker-format');
var fpFormat = momentToFlatpickr(momentFormat);

var fp = flatpickr(input, {
dateFormat: fpFormat,
allowInput: true, // user can also type manually
disableMobile: true, // always use the custom picker
static: true, // render inline below the input to avoid
// overflow clipping in the Joplin dialog
onReady: function () {
addYearDropdown(fp);
},
});
});
}

// Run once the DOM is ready.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDatePickers);
} else {
initDatePickers();
}
})();
13 changes: 13 additions & 0 deletions src/views/flatpickr.min.css

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/views/flatpickr.min.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/views/templateVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { AUTO_FOCUS_SCRIPT } from "../utils/dialogHelpers";

export const setTemplateVariablesView = async (viewHandle: string, title: string, variables: Record<string, CustomVariable>): Promise<void> => {
await joplin.views.dialogs.addScript(viewHandle, "./views/webview.css");
await joplin.views.dialogs.addScript(viewHandle, "./views/flatpickr.min.css");
await joplin.views.dialogs.addScript(viewHandle, "./views/flatpickr.min.js");
await joplin.views.dialogs.addScript(viewHandle, "./views/datepicker.js");

const variablesFormInputHtml = Object.values(variables).map(variable => variable.toHTML());

Expand Down
Loading
Loading