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
9 changes: 9 additions & 0 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 @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
6 changes: 3 additions & 3 deletions src/variables/types/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
this.label = label;
}

protected inputHTML(): string {
protected inputHTML(dateAndTimeUtils?: DateAndTimeUtils): string {

Check warning on line 16 in src/variables/types/base.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

'dateAndTimeUtils' is defined but never used
return "";
}

public toHTML(): string {
public toHTML(dateAndTimeUtils?: DateAndTimeUtils): string {
return (
`
<div class="variableInput">
<div class="variableName">
${encode(this.label)}
</div>
<div>
${this.inputHTML()}
${this.inputHTML(dateAndTimeUtils)}
</div>
</div>
`
Expand Down
32 changes: 28 additions & 4 deletions src/variables/types/date.ts
Original file line number Diff line number Diff line change
@@ -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 `<input name="${encode(this.name)}" type="date" aria-label="${encode(this.label)}"></input>`;
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 `<input name="${encode(this.name)}" type="text" data-datepicker-format="${encode(dateFormat)}" placeholder="${encode(dateFormat)}" autocomplete="off" aria-label="${encode(this.label)}"></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();
}
})();
143 changes: 143 additions & 0 deletions src/views/flatpicker-overrides.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* ── Flatpickr: make the calendar fit the dialog and match Joplin theme ── */

.flatpickr-calendar {
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
font-family: var(--joplin-font-family) !important;
font-size: 13px !important;
background: var(--joplin-background-color) !important;
color: var(--joplin-color) !important;
border: 1px solid var(--joplin-divider-color) !important;
box-shadow: none !important;
}

/* Month/year header row */
.flatpickr-months {
display: flex !important;
align-items: center !important;
overflow: visible !important;
background: var(--joplin-background-color) !important;
color: var(--joplin-color) !important;
}

.flatpickr-month {
flex: 1 !important;
height: auto !important;
overflow: visible !important;
background: var(--joplin-background-color) !important;
color: var(--joplin-color) !important;
}

.flatpickr-current-month {
display: flex !important;
align-items: center !important;
justify-content: center !important;
gap: 4px !important;
padding: 6px 0 !important;
position: static !important;
width: 100% !important;
font-size: 13px !important;
color: var(--joplin-color) !important;
}

.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month .numInputWrapper {
color: var(--joplin-color) !important;
background: transparent !important;
font-size: 13px !important;
}

.flatpickr-current-month input.cur-year {
color: var(--joplin-color) !important;
font-size: 13px !important;
}

/* Prev/next month arrows */
.flatpickr-prev-month,
.flatpickr-next-month {
color: var(--joplin-color) !important;
fill: var(--joplin-color) !important;
}

/* Inner container and day grid — override hardcoded pixel widths */
.flatpickr-rContainer {
width: 100% !important;
}

.flatpickr-weekdays {
width: 100% !important;
display: flex !important;
background: transparent !important;
}

.flatpickr-weekday {
flex: 1 !important;
max-width: unset !important;
font-size: 12px !important;
color: var(--joplin-color) !important;
background: transparent !important;
}

.flatpickr-days {
width: 100% !important;
}

.dayContainer {
width: 100% !important;
min-width: unset !important;
max-width: 100% !important;
display: flex !important;
flex-wrap: wrap !important;
justify-content: space-around !important;
}

.flatpickr-day {
flex: 1 1 13% !important;
max-width: 13% !important;
height: 32px !important;
line-height: 32px !important;
font-size: 12px !important;
color: var(--joplin-color) !important;
border-radius: 4px !important;
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover {
background: #4a90d9 !important;
border-color: #4a90d9 !important;
color: #fff !important;
}

.flatpickr-day:hover {
background: rgba(255, 255, 255, 0.1) !important;
}

.flatpickr-day.today {
border-color: #4a90d9 !important;
}

.flatpickr-day.flatpickr-disabled,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
opacity: 0.3 !important;
}

/* Custom year dropdown added by datepicker.js */
select.year-select {
color: var(--joplin-color) !important;
background: var(--joplin-background-color) !important;
border: 1px solid var(--joplin-divider-color) !important;
border-radius: 3px !important;
font-size: 13px !important;
font-family: var(--joplin-font-family) !important;
padding: 2px 4px !important;
height: auto !important;
width: auto !important;
cursor: pointer !important;
}

select.year-select:focus {
outline: none !important;
border-color: var(--joplin-color) !important;
}
10 changes: 8 additions & 2 deletions src/views/templateVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import { encode } from "html-entities";
import { CustomVariable } from "../variables/types/base";
import { AUTO_FOCUS_SCRIPT } from "../utils/dialogHelpers";

export const setTemplateVariablesView = async (viewHandle: string, title: string, variables: Record<string, CustomVariable>): Promise<void> => {
import { DateAndTimeUtils } from "../utils/dateAndTime";

export const setTemplateVariablesView = async (viewHandle: string, title: string, variables: Record<string, CustomVariable>, utils?: DateAndTimeUtils): Promise<void> => {
await joplin.views.dialogs.addScript(viewHandle, "./views/webview.css");
await joplin.views.dialogs.addScript(viewHandle, "./views/flatpicker-overrides.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());
const variablesFormInputHtml = Object.values(variables).map(variable => variable.toHTML(utils));

// Add id for autofocus hack to the first input element
let formHtml = variablesFormInputHtml.join("");
Expand Down
8 changes: 6 additions & 2 deletions src/views/webview.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ th, td {
padding: 2px;
}

input, select, textarea {
input,
select,
textarea {
height: 25px;
font-size: var(--joplin-font-size, 16px);
font-family: var(--joplin-font-family);
Expand All @@ -42,7 +44,9 @@ input, select, textarea {
border-radius: 3px;
}

input:focus, select:focus, textarea:focus {
input:focus,
select:focus,
textarea:focus {
outline: none;
border-color: var(--joplin-color);
}
Expand Down
Loading
Loading