mirror of
https://github.com/mildeveloper/obsidian-date-range-expander.git
synced 2026-07-22 05:43:20 +00:00
643 lines
83 KiB
JavaScript
643 lines
83 KiB
JavaScript
|
|
/*
|
||
|
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||
|
|
if you want to view the source, please visit the github repository of this plugin
|
||
|
|
*/
|
||
|
|
|
||
|
|
var __defProp = Object.defineProperty;
|
||
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||
|
|
var __export = (target, all) => {
|
||
|
|
for (var name in all)
|
||
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||
|
|
};
|
||
|
|
var __copyProps = (to, from, except, desc) => {
|
||
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
||
|
|
for (let key of __getOwnPropNames(from))
|
||
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||
|
|
}
|
||
|
|
return to;
|
||
|
|
};
|
||
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||
|
|
|
||
|
|
// src/main.ts
|
||
|
|
var main_exports = {};
|
||
|
|
__export(main_exports, {
|
||
|
|
default: () => DateRangeExpanderPlugin
|
||
|
|
});
|
||
|
|
module.exports = __toCommonJS(main_exports);
|
||
|
|
var import_obsidian4 = require("obsidian");
|
||
|
|
|
||
|
|
// src/dateInputModal.ts
|
||
|
|
var import_obsidian = require("obsidian");
|
||
|
|
|
||
|
|
// src/dateUtils.ts
|
||
|
|
var DateUtils = class {
|
||
|
|
getStartAndEndDates(modalResponse) {
|
||
|
|
let startDate = modalResponse.startDate;
|
||
|
|
let endDate;
|
||
|
|
if (modalResponse.rangeType === "EndDate") {
|
||
|
|
endDate = modalResponse.endDate;
|
||
|
|
} else {
|
||
|
|
endDate = new Date(startDate);
|
||
|
|
const duration = modalResponse.duration;
|
||
|
|
const unit = modalResponse.durationUnit;
|
||
|
|
switch (unit) {
|
||
|
|
case "Days":
|
||
|
|
endDate.setDate(startDate.getDate() + duration - 1);
|
||
|
|
break;
|
||
|
|
case "Weeks":
|
||
|
|
endDate.setDate(startDate.getDate() + duration * 7 - 1);
|
||
|
|
break;
|
||
|
|
case "Months":
|
||
|
|
endDate = this.addMonthsToDate(startDate, duration);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (startDate > endDate) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const dateStartAndEnd = {
|
||
|
|
startDate,
|
||
|
|
endDate
|
||
|
|
};
|
||
|
|
return dateStartAndEnd;
|
||
|
|
}
|
||
|
|
// Assuming string date is in format: YYYYMMDD
|
||
|
|
parseDateFromString(dateString) {
|
||
|
|
if (!this.isValidNumericDate(dateString)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const year = parseInt(dateString.substring(0, 4));
|
||
|
|
const month = parseInt(dateString.substring(4, 6)) - 1;
|
||
|
|
const day = parseInt(dateString.substring(6, 8));
|
||
|
|
const date = new Date(Date.UTC(year, month, day));
|
||
|
|
return date;
|
||
|
|
}
|
||
|
|
// Returns true if string date is in format: YYYYMMDD
|
||
|
|
isValidNumericDate(dateString) {
|
||
|
|
if (!/^\d{8}$/.test(dateString))
|
||
|
|
return false;
|
||
|
|
const year = parseInt(dateString.substring(0, 4));
|
||
|
|
const month = parseInt(dateString.substring(4, 6)) - 1;
|
||
|
|
const day = parseInt(dateString.substring(6, 8));
|
||
|
|
if (year < 1900)
|
||
|
|
return false;
|
||
|
|
if (month < 0 || month > 11)
|
||
|
|
return false;
|
||
|
|
if (day < 1 || day > 31)
|
||
|
|
return false;
|
||
|
|
const dateObj = new Date(year, month, day);
|
||
|
|
return dateObj.getFullYear() === year && dateObj.getMonth() === month && dateObj.getDate() === day;
|
||
|
|
}
|
||
|
|
formatDateToString(date, settingsFormat, defaultFormat) {
|
||
|
|
const year = date.getFullYear();
|
||
|
|
const month = date.getMonth();
|
||
|
|
const day = date.getDate();
|
||
|
|
const weekday = date.getDay();
|
||
|
|
const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||
|
|
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||
|
|
const dayAbbreviations = dayNames.map((day2) => day2.slice(0, 3));
|
||
|
|
const monthAbbreviations = monthNames.map((month2) => month2.slice(0, 3));
|
||
|
|
const formatDate = (format) => {
|
||
|
|
return format.replace(/YYYY|YYY|YY|Y|MMMM|MMM|MM|M|DDDD|DDD|DD|D/g, (match) => {
|
||
|
|
switch (match) {
|
||
|
|
case "YYYY":
|
||
|
|
return year.toString();
|
||
|
|
case "YYY":
|
||
|
|
return year.toString().slice(-3);
|
||
|
|
case "YY":
|
||
|
|
return year.toString().slice(-2);
|
||
|
|
case "Y":
|
||
|
|
return year.toString().slice(-1);
|
||
|
|
case "MMMM":
|
||
|
|
return monthNames[month];
|
||
|
|
case "MMM":
|
||
|
|
return monthAbbreviations[month];
|
||
|
|
case "MM":
|
||
|
|
return (month + 1).toString().padStart(2, "0");
|
||
|
|
case "M":
|
||
|
|
return (month + 1).toString();
|
||
|
|
case "DDDD":
|
||
|
|
return dayNames[weekday];
|
||
|
|
case "DDD":
|
||
|
|
return dayAbbreviations[weekday];
|
||
|
|
case "DD":
|
||
|
|
return day.toString().padStart(2, "0");
|
||
|
|
case "D":
|
||
|
|
return day.toString();
|
||
|
|
default:
|
||
|
|
return match;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
try {
|
||
|
|
return formatDate(settingsFormat);
|
||
|
|
} catch (error) {
|
||
|
|
return formatDate(defaultFormat);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
addMonthsToDate(date, months) {
|
||
|
|
if (months === 0) {
|
||
|
|
return date;
|
||
|
|
}
|
||
|
|
const newDate = new Date(date);
|
||
|
|
const currentDay = date.getDate();
|
||
|
|
newDate.setMonth(newDate.getMonth() + months);
|
||
|
|
newDate.setDate(newDate.getDate() - 1);
|
||
|
|
if (newDate.getMonth() !== date.getMonth() && newDate.getDate() !== currentDay - 1) {
|
||
|
|
newDate.setDate(0);
|
||
|
|
}
|
||
|
|
return newDate;
|
||
|
|
}
|
||
|
|
calculateDaysInRange(startDate, endDate) {
|
||
|
|
const millisecondsInDay = 1e3 * 60 * 60 * 24;
|
||
|
|
const differenceMilliseconds = endDate.getTime() - startDate.getTime();
|
||
|
|
const differenceDays = Math.floor(differenceMilliseconds / millisecondsInDay) + 1;
|
||
|
|
return Math.max(0, differenceDays);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/dateInputModal.ts
|
||
|
|
var DateInputModal = class extends import_obsidian.Modal {
|
||
|
|
constructor(app, onSubmit) {
|
||
|
|
super(app);
|
||
|
|
this.startDateValue = "";
|
||
|
|
this.endDateValue = "";
|
||
|
|
this.durationValue = "";
|
||
|
|
this.durationTypeValue = "Days";
|
||
|
|
this.useCalloutValue = true;
|
||
|
|
this.rangeTypeValue = "EndDate";
|
||
|
|
this.submitButton = null;
|
||
|
|
this.onSubmit = onSubmit;
|
||
|
|
this.dateUtils = new DateUtils();
|
||
|
|
this.setTitle("Enter date range");
|
||
|
|
}
|
||
|
|
onOpen() {
|
||
|
|
const { contentEl } = this;
|
||
|
|
contentEl.empty();
|
||
|
|
this.addStyles();
|
||
|
|
this.setupKeyboardListener();
|
||
|
|
this.createStartDateInput();
|
||
|
|
this.createRangeOptions();
|
||
|
|
this.createCalloutToggle();
|
||
|
|
this.createSubmitButton();
|
||
|
|
}
|
||
|
|
onClose() {
|
||
|
|
const { contentEl } = this;
|
||
|
|
if (this.keyboardListener) {
|
||
|
|
contentEl.removeEventListener("keydown", this.keyboardListener);
|
||
|
|
}
|
||
|
|
contentEl.empty();
|
||
|
|
}
|
||
|
|
validateInput() {
|
||
|
|
const startDateObj = this.dateUtils.parseDateFromString(this.startDateValue);
|
||
|
|
if (!startDateObj) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (this.rangeTypeValue === "EndDate") {
|
||
|
|
const endDateObj = this.dateUtils.parseDateFromString(this.endDateValue);
|
||
|
|
return !!endDateObj && this.dateUtils.isValidNumericDate(this.endDateValue) && endDateObj >= startDateObj;
|
||
|
|
} else {
|
||
|
|
return this.durationValue !== "" && parseInt(this.durationValue) > 0 && ["Days", "Weeks", "Months"].includes(this.durationTypeValue);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
calculateDateCount() {
|
||
|
|
const startDate = this.dateUtils.parseDateFromString(this.startDateValue);
|
||
|
|
if (!startDate) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
let endDate;
|
||
|
|
if (this.rangeTypeValue === "EndDate") {
|
||
|
|
endDate = this.dateUtils.parseDateFromString(this.endDateValue) || startDate;
|
||
|
|
} else {
|
||
|
|
endDate = new Date(startDate);
|
||
|
|
const duration = parseInt(this.durationValue) || 0;
|
||
|
|
if (this.durationTypeValue === "Days") {
|
||
|
|
endDate.setDate(endDate.getDate() + duration - 1);
|
||
|
|
} else if (this.durationTypeValue === "Weeks") {
|
||
|
|
endDate.setDate(endDate.getDate() + duration * 7 - 1);
|
||
|
|
} else if (this.durationTypeValue === "Months") {
|
||
|
|
endDate = this.dateUtils.addMonthsToDate(startDate, duration);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const dateCount = this.dateUtils.calculateDaysInRange(startDate, endDate);
|
||
|
|
return dateCount;
|
||
|
|
}
|
||
|
|
addStyles() {
|
||
|
|
const styleEl = document.createElement("style");
|
||
|
|
styleEl.textContent = `
|
||
|
|
.date-option-container {
|
||
|
|
margin-bottom: 15px;
|
||
|
|
}
|
||
|
|
.date-input-row {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
}
|
||
|
|
.date-input-row input[type="radio"] {
|
||
|
|
margin-right: 8px;
|
||
|
|
}
|
||
|
|
.date-input-row input[type="text"],
|
||
|
|
.date-input-row input[type="number"] {
|
||
|
|
margin-left: 5px;
|
||
|
|
}
|
||
|
|
.date-input-row select {
|
||
|
|
margin-left: 5px;
|
||
|
|
}
|
||
|
|
`;
|
||
|
|
this.contentEl.appendChild(styleEl);
|
||
|
|
}
|
||
|
|
setupKeyboardListener() {
|
||
|
|
this.keyboardListener = (event) => {
|
||
|
|
if (event.key === "Enter" && this.validateInput()) {
|
||
|
|
this.submit();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
this.contentEl.addEventListener("keydown", this.keyboardListener);
|
||
|
|
}
|
||
|
|
createStartDateInput() {
|
||
|
|
new import_obsidian.Setting(this.contentEl).setName("Start date").addText((text) => {
|
||
|
|
text.setPlaceholder("YYYYMMDD").setValue(this.startDateValue);
|
||
|
|
text.inputEl.setAttribute("pattern", "[0-9]*");
|
||
|
|
text.inputEl.setAttribute("maxlength", "8");
|
||
|
|
text.inputEl.addEventListener("input", (e) => {
|
||
|
|
const input = e.target;
|
||
|
|
input.value = input.value.replace(/\D/g, "").slice(0, 8);
|
||
|
|
this.startDateValue = input.value;
|
||
|
|
this.updateSubmitButton();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
createRangeOptions() {
|
||
|
|
const optionContainer = this.contentEl.createEl("div", { cls: "date-option-container" });
|
||
|
|
this.createEndDateOption(optionContainer);
|
||
|
|
this.createDurationOption(optionContainer);
|
||
|
|
this.updateInputStates();
|
||
|
|
}
|
||
|
|
createEndDateOption(container) {
|
||
|
|
const endDateRow = container.createEl("div", { cls: "date-input-row" });
|
||
|
|
const endDateLabel = endDateRow.createEl("label");
|
||
|
|
endDateLabel.appendText("End date");
|
||
|
|
const endDateRadio = endDateLabel.createEl("input", {
|
||
|
|
type: "radio",
|
||
|
|
attr: { name: "dateOption" }
|
||
|
|
});
|
||
|
|
endDateRadio.checked = this.rangeTypeValue === "EndDate";
|
||
|
|
this.endDateInput = endDateRow.createEl("input", {
|
||
|
|
type: "text",
|
||
|
|
placeholder: "YYYYMMDD",
|
||
|
|
value: this.endDateValue,
|
||
|
|
attr: {
|
||
|
|
pattern: "[0-9]*",
|
||
|
|
maxlength: "8"
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.endDateInput.addEventListener("input", (e) => {
|
||
|
|
const input = e.target;
|
||
|
|
input.value = input.value.replace(/\D/g, "").slice(0, 8);
|
||
|
|
this.endDateValue = input.value;
|
||
|
|
this.updateSubmitButton();
|
||
|
|
});
|
||
|
|
endDateRadio.addEventListener("change", () => {
|
||
|
|
this.rangeTypeValue = "EndDate";
|
||
|
|
this.updateInputStates();
|
||
|
|
this.updateSubmitButton();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
createDurationOption(container) {
|
||
|
|
const durationRow = container.createEl("div", { cls: "date-input-row" });
|
||
|
|
const durationLabel = durationRow.createEl("label");
|
||
|
|
durationLabel.appendText("Duration");
|
||
|
|
const durationRadio = durationLabel.createEl("input", {
|
||
|
|
type: "radio",
|
||
|
|
attr: { name: "dateOption" }
|
||
|
|
});
|
||
|
|
durationRadio.checked = this.rangeTypeValue === "Duration";
|
||
|
|
this.durationInput = durationRow.createEl("input", {
|
||
|
|
type: "number",
|
||
|
|
attr: {
|
||
|
|
min: "1",
|
||
|
|
max: "100"
|
||
|
|
},
|
||
|
|
placeholder: "1-100",
|
||
|
|
value: this.durationValue
|
||
|
|
});
|
||
|
|
this.durationTypeSelect = durationRow.createEl("select");
|
||
|
|
this.populateDurationTypes();
|
||
|
|
durationRadio.addEventListener("change", () => {
|
||
|
|
this.rangeTypeValue = "Duration";
|
||
|
|
this.updateInputStates();
|
||
|
|
this.updateSubmitButton();
|
||
|
|
});
|
||
|
|
this.durationInput.addEventListener("input", (e) => {
|
||
|
|
const input = e.target;
|
||
|
|
let value = parseInt(input.value);
|
||
|
|
if (value > 100) {
|
||
|
|
value = 100;
|
||
|
|
input.value = "100";
|
||
|
|
}
|
||
|
|
this.durationValue = value.toString();
|
||
|
|
this.updateSubmitButton();
|
||
|
|
});
|
||
|
|
this.durationTypeSelect.addEventListener("change", (e) => {
|
||
|
|
this.durationTypeValue = e.target.value;
|
||
|
|
this.updateSubmitButton();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
populateDurationTypes() {
|
||
|
|
["Days", "Weeks", "Months"].forEach((type) => {
|
||
|
|
const option = this.durationTypeSelect.createEl("option", {
|
||
|
|
value: type,
|
||
|
|
text: type
|
||
|
|
});
|
||
|
|
option.selected = type === this.durationTypeValue;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
updateInputStates() {
|
||
|
|
const isEndDateMode = this.rangeTypeValue === "EndDate";
|
||
|
|
this.endDateInput.disabled = !isEndDateMode;
|
||
|
|
this.durationInput.disabled = isEndDateMode;
|
||
|
|
this.durationTypeSelect.disabled = isEndDateMode;
|
||
|
|
}
|
||
|
|
createCalloutToggle() {
|
||
|
|
new import_obsidian.Setting(this.contentEl).setName("Add to callout").setDesc("Insert dates inside a collapsed callout").addToggle(
|
||
|
|
(toggle) => toggle.setValue(this.useCalloutValue).onChange((value) => {
|
||
|
|
this.useCalloutValue = value;
|
||
|
|
})
|
||
|
|
);
|
||
|
|
}
|
||
|
|
createSubmitButton() {
|
||
|
|
this.submitButton = new import_obsidian.Setting(this.contentEl).addButton(
|
||
|
|
(btn) => btn.setButtonText("Insert").setCta().onClick(() => this.submit())
|
||
|
|
).settingEl.querySelector("button");
|
||
|
|
if (this.submitButton) {
|
||
|
|
this.submitButton.disabled = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
updateSubmitButton() {
|
||
|
|
if (this.submitButton) {
|
||
|
|
const validInput = this.validateInput();
|
||
|
|
this.submitButton.disabled = !validInput;
|
||
|
|
if (validInput) {
|
||
|
|
const dateCount = this.calculateDateCount();
|
||
|
|
this.submitButton.textContent = `Insert ${dateCount} Date${dateCount !== 1 ? "s" : ""}`;
|
||
|
|
} else {
|
||
|
|
this.submitButton.textContent = "Insert";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
submit() {
|
||
|
|
if (!this.validateInput()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const startDateObj = this.dateUtils.parseDateFromString(this.startDateValue);
|
||
|
|
if (!startDateObj) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const dateRangeValues = {
|
||
|
|
startDate: startDateObj,
|
||
|
|
rangeType: this.rangeTypeValue,
|
||
|
|
useCallout: this.useCalloutValue
|
||
|
|
};
|
||
|
|
if (this.rangeTypeValue === "EndDate") {
|
||
|
|
const endDateObj = this.dateUtils.parseDateFromString(this.endDateValue);
|
||
|
|
if (endDateObj)
|
||
|
|
dateRangeValues.endDate = endDateObj;
|
||
|
|
} else {
|
||
|
|
dateRangeValues.duration = parseInt(this.durationValue);
|
||
|
|
dateRangeValues.durationUnit = this.durationTypeValue;
|
||
|
|
}
|
||
|
|
this.close();
|
||
|
|
if (this.onSubmit) {
|
||
|
|
this.onSubmit(dateRangeValues);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/types.ts
|
||
|
|
var DEFAULT_SETTINGS = {
|
||
|
|
outputDateFormat: "YYYY.MM.DD",
|
||
|
|
friendlyDateFormat: "DDD D MMM YYYY",
|
||
|
|
dateSeparator: ", ",
|
||
|
|
createWikiLinks: true,
|
||
|
|
createNonExistentFiles: "same-folder",
|
||
|
|
customFolderPath: ""
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/settingsTab.ts
|
||
|
|
var import_obsidian2 = require("obsidian");
|
||
|
|
var DateRangeExpanderSettingTab = class extends import_obsidian2.PluginSettingTab {
|
||
|
|
constructor(app, plugin) {
|
||
|
|
super(app, plugin);
|
||
|
|
this.plugin = plugin;
|
||
|
|
}
|
||
|
|
display() {
|
||
|
|
const { containerEl } = this;
|
||
|
|
containerEl.empty();
|
||
|
|
this.createDateFormatSettings();
|
||
|
|
this.createWikiLinkSettings();
|
||
|
|
}
|
||
|
|
createDateFormatSettings() {
|
||
|
|
new import_obsidian2.Setting(this.containerEl).setName("Output date format").setDesc(`Format for output dates (e.g., ${DEFAULT_SETTINGS.outputDateFormat})`).addText((text) => text.setPlaceholder(DEFAULT_SETTINGS.outputDateFormat).setValue(this.plugin.settings.outputDateFormat).onChange(async (value) => {
|
||
|
|
this.plugin.settings.outputDateFormat = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
}));
|
||
|
|
new import_obsidian2.Setting(this.containerEl).setName("Friendly date format").setDesc(`Format for friendly date display (e.g., ${DEFAULT_SETTINGS.friendlyDateFormat}). Supports D, DD, DDD, DDDD, M, MM, MMM, MMMM, Y, YY, YYY, YYYYM.`).addText((text) => text.setPlaceholder(DEFAULT_SETTINGS.friendlyDateFormat).setValue(this.plugin.settings.friendlyDateFormat).onChange(async (value) => {
|
||
|
|
this.plugin.settings.friendlyDateFormat = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
}));
|
||
|
|
new import_obsidian2.Setting(this.containerEl).setName("Date separator").setDesc(`Separator between dates (e.g., "${DEFAULT_SETTINGS.dateSeparator}")`).addText((text) => text.setPlaceholder(DEFAULT_SETTINGS.dateSeparator).setValue(this.plugin.settings.dateSeparator).onChange(async (value) => {
|
||
|
|
this.plugin.settings.dateSeparator = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
createWikiLinkSettings() {
|
||
|
|
new import_obsidian2.Setting(this.containerEl).setName("Create wiki links").setDesc("Create wiki links for inserted dates").addToggle((toggle) => toggle.setValue(this.plugin.settings.createWikiLinks).onChange(async (value) => {
|
||
|
|
this.plugin.settings.createWikiLinks = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.display();
|
||
|
|
}));
|
||
|
|
if (this.plugin.settings.createWikiLinks) {
|
||
|
|
this.createNonExistentFilesSettings();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
createNonExistentFilesSettings() {
|
||
|
|
new import_obsidian2.Setting(this.containerEl).setName("Create non-existent files").setDesc("What to do if a wiki linked file does not exist").addDropdown((dropdown) => dropdown.addOption("do-not-create", "Do not create").addOption("same-folder", "Create in same folder").addOption("custom-folder", "Create in custom folder").setValue(this.plugin.settings.createNonExistentFiles).onChange(async (value) => {
|
||
|
|
this.plugin.settings.createNonExistentFiles = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.display();
|
||
|
|
}));
|
||
|
|
if (this.plugin.settings.createNonExistentFiles === "custom-folder") {
|
||
|
|
this.createCustomFolderPathSetting();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
createCustomFolderPathSetting() {
|
||
|
|
new import_obsidian2.Setting(this.containerEl).setName("Custom folder path").setDesc('Path to the folder where new files should be created (e.g., "Daily Notes/2025")').addText((text) => {
|
||
|
|
text.setPlaceholder("Enter folder path").setValue(this.plugin.settings.customFolderPath || "").onChange(async (value) => {
|
||
|
|
this.plugin.settings.customFolderPath = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/dateRangeExpander.ts
|
||
|
|
var import_obsidian3 = require("obsidian");
|
||
|
|
var DateRangeExpander = class {
|
||
|
|
constructor(app, settings, dateUtils) {
|
||
|
|
this.app = app;
|
||
|
|
this.settings = settings;
|
||
|
|
this.dateUtils = dateUtils;
|
||
|
|
}
|
||
|
|
wrapInCallout(dateStartAndEnd, insertedDateRange) {
|
||
|
|
const [startDateFormat, endDateFormat] = this.getFormatedStartAndEndDates(dateStartAndEnd);
|
||
|
|
return `
|
||
|
|
> [!SUMMARY]- Date range: ${startDateFormat} to ${endDateFormat}
|
||
|
|
> ${insertedDateRange}
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
expandDateRange(dateStartAndEnd) {
|
||
|
|
let totalDates = 0;
|
||
|
|
let createdFiles = 0;
|
||
|
|
let existingFiles = 0;
|
||
|
|
let customFolderExists = true;
|
||
|
|
let dates = [];
|
||
|
|
let current = new Date(dateStartAndEnd.startDate);
|
||
|
|
const customFolder = this.app.vault.getAbstractFileByPath(this.settings.customFolderPath);
|
||
|
|
if (this.settings.createNonExistentFiles === "custom-folder" && !(customFolder instanceof import_obsidian3.TFolder)) {
|
||
|
|
customFolderExists = false;
|
||
|
|
}
|
||
|
|
while (current <= dateStartAndEnd.endDate) {
|
||
|
|
let formattedDate = this.dateUtils.formatDateToString(current, this.settings.outputDateFormat, DEFAULT_SETTINGS.outputDateFormat);
|
||
|
|
if (this.settings.createWikiLinks) {
|
||
|
|
dates.push(`[[${formattedDate}]]`);
|
||
|
|
const fileCreationResult = this.createFileIfNeeded(formattedDate, customFolderExists);
|
||
|
|
if (fileCreationResult === "created")
|
||
|
|
createdFiles++;
|
||
|
|
else if (fileCreationResult === "exists")
|
||
|
|
existingFiles++;
|
||
|
|
} else {
|
||
|
|
dates.push(formattedDate);
|
||
|
|
}
|
||
|
|
totalDates++;
|
||
|
|
current.setDate(current.getDate() + 1);
|
||
|
|
}
|
||
|
|
if (createdFiles > 0 && !customFolderExists) {
|
||
|
|
new import_obsidian3.Notice(`Folder "${this.settings.customFolderPath}" does not exist. Using parent folder to current note instead.`);
|
||
|
|
}
|
||
|
|
const [startDateFormat, endDateFormat] = this.getFormatedStartAndEndDates(dateStartAndEnd);
|
||
|
|
const commonNoticeText = `Inserted ${totalDates} dates from ${startDateFormat} to ${endDateFormat}`;
|
||
|
|
if (this.settings.createNonExistentFiles !== "do-not-create") {
|
||
|
|
new import_obsidian3.Notice(`${commonNoticeText}
|
||
|
|
|
||
|
|
\u2022 Files created: ${createdFiles}
|
||
|
|
\u2022 Files skipped: ${existingFiles}`, 8e3);
|
||
|
|
} else {
|
||
|
|
new import_obsidian3.Notice(commonNoticeText, 8e3);
|
||
|
|
}
|
||
|
|
return dates.join(this.settings.dateSeparator);
|
||
|
|
}
|
||
|
|
createFileIfNeeded(formattedDate, customFolderExists) {
|
||
|
|
const currentFile = this.app.workspace.getActiveFile();
|
||
|
|
if (!currentFile || !currentFile.parent) {
|
||
|
|
return "failed";
|
||
|
|
}
|
||
|
|
const fileName = `${formattedDate}.md`;
|
||
|
|
const fileExists = this.fileExistsInVault(formattedDate);
|
||
|
|
if (!fileExists) {
|
||
|
|
let filePath = "";
|
||
|
|
switch (this.settings.createNonExistentFiles) {
|
||
|
|
case "same-folder":
|
||
|
|
const currentDir = currentFile.parent.path;
|
||
|
|
filePath = currentDir ? `${currentDir}/${fileName}` : fileName;
|
||
|
|
break;
|
||
|
|
case "custom-folder":
|
||
|
|
if (customFolderExists) {
|
||
|
|
filePath = `${this.settings.customFolderPath}/${fileName}`;
|
||
|
|
} else {
|
||
|
|
const currentDir2 = currentFile.parent.path;
|
||
|
|
filePath = currentDir2 ? `${currentDir2}/${fileName}` : fileName;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case "do-not-create":
|
||
|
|
return "failed";
|
||
|
|
default:
|
||
|
|
return "failed";
|
||
|
|
}
|
||
|
|
if (filePath) {
|
||
|
|
this.app.vault.create(filePath, "");
|
||
|
|
return "created";
|
||
|
|
}
|
||
|
|
return "failed";
|
||
|
|
} else {
|
||
|
|
return "exists";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
fileExistsInVault(formattedDate) {
|
||
|
|
return this.app.metadataCache.getFirstLinkpathDest(formattedDate, "") !== null;
|
||
|
|
}
|
||
|
|
getFormatedStartAndEndDates(dateStartAndEnd) {
|
||
|
|
const startDateFormat = this.dateUtils.formatDateToString(dateStartAndEnd.startDate, this.settings.friendlyDateFormat, DEFAULT_SETTINGS.friendlyDateFormat);
|
||
|
|
const endDateFormat = this.dateUtils.formatDateToString(dateStartAndEnd.endDate, this.settings.friendlyDateFormat, DEFAULT_SETTINGS.friendlyDateFormat);
|
||
|
|
return [startDateFormat, endDateFormat];
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/main.ts
|
||
|
|
var DateRangeExpanderPlugin = class extends import_obsidian4.Plugin {
|
||
|
|
async onload() {
|
||
|
|
console.log("DateRangeExpander loaded");
|
||
|
|
await this.loadSettings();
|
||
|
|
this.dateUtils = new DateUtils();
|
||
|
|
this.dateRangeExpander = new DateRangeExpander(this.app, this.settings, this.dateUtils);
|
||
|
|
this.addSettingTab(new DateRangeExpanderSettingTab(this.app, this));
|
||
|
|
this.addCommand({
|
||
|
|
id: "insert-expanded-date-range",
|
||
|
|
name: "Insert",
|
||
|
|
checkCallback: (checking) => {
|
||
|
|
const markdownView = this.app.workspace.getActiveViewOfType(import_obsidian4.MarkdownView);
|
||
|
|
if (markdownView) {
|
||
|
|
if (!checking) {
|
||
|
|
new DateInputModal(this.app, (rangeInput) => {
|
||
|
|
const dateStartAndEnd = this.dateUtils.getStartAndEndDates(rangeInput);
|
||
|
|
if (dateStartAndEnd) {
|
||
|
|
const editor = this.getActiveEditor();
|
||
|
|
if (editor) {
|
||
|
|
const cursor = editor.getCursor();
|
||
|
|
let insertedDateRange = this.dateRangeExpander.expandDateRange(dateStartAndEnd) + " ";
|
||
|
|
if (rangeInput.useCallout) {
|
||
|
|
insertedDateRange = this.dateRangeExpander.wrapInCallout(dateStartAndEnd, insertedDateRange);
|
||
|
|
}
|
||
|
|
editor.replaceRange(insertedDateRange, { line: cursor.line, ch: cursor.ch }, cursor);
|
||
|
|
const endPos = { line: cursor.line, ch: cursor.ch + insertedDateRange.length };
|
||
|
|
editor.setCursor(endPos);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).open();
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
async loadSettings() {
|
||
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||
|
|
}
|
||
|
|
async saveSettings() {
|
||
|
|
await this.saveData(this.settings);
|
||
|
|
}
|
||
|
|
onunload() {
|
||
|
|
console.log("DateRangeExpander unloaded");
|
||
|
|
}
|
||
|
|
getActiveEditor() {
|
||
|
|
const activeLeaf = this.app.workspace.getActiveViewOfType(import_obsidian4.MarkdownView);
|
||
|
|
if (activeLeaf) {
|
||
|
|
return activeLeaf.editor;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3JjL21haW4udHMiLCAic3JjL2RhdGVJbnB1dE1vZGFsLnRzIiwgInNyYy9kYXRlVXRpbHMudHMiLCAic3JjL3R5cGVzLnRzIiwgInNyYy9zZXR0aW5nc1RhYi50cyIsICJzcmMvZGF0ZVJhbmdlRXhwYW5kZXIudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImltcG9ydCB7IE1hcmtkb3duVmlldywgUGx1Z2luIH0gZnJvbSAnb2JzaWRpYW4nO1xyXG5pbXBvcnQgeyBEYXRlSW5wdXRNb2RhbCB9IGZyb20gJy4vZGF0ZUlucHV0TW9kYWwnO1xyXG5pbXBvcnQgeyBERUZBVUxUX1NFVFRJTkdTLCBQbHVnaW5TZXR0aW5ncyB9IGZyb20gJy4vdHlwZXMnO1xyXG5pbXBvcnQgeyBEYXRlUmFuZ2VFeHBhbmRlclNldHRpbmdUYWIgfSBmcm9tICcuL3NldHRpbmdzVGFiJztcclxuaW1wb3J0IHsgRGF0ZVV0aWxzIH0gZnJvbSAnLi9kYXRlVXRpbHMnO1xyXG5pbXBvcnQgeyBEYXRlUmFuZ2VFeHBhbmRlciB9IGZyb20gJy4vZGF0ZVJhbmdlRXhwYW5kZXInO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgRGF0ZVJhbmdlRXhwYW5kZXJQbHVnaW4gZXh0ZW5kcyBQbHVnaW4ge1xyXG5cdHNldHRpbmdzOiBQbHVnaW5TZXR0aW5ncztcclxuXHRkYXRlUmFuZ2VFeHBhbmRlcjogRGF0ZVJhbmdlRXhwYW5kZXI7XHJcblx0ZGF0ZVV0aWxzOiBEYXRlVXRpbHM7XHJcblxyXG5cdGFzeW5jIG9ubG9hZCgpIHtcclxuXHRcdGNvbnNvbGUubG9nKCdEYXRlUmFuZ2VFeHBhbmRlciBsb2FkZWQnKTtcclxuXHRcdFxyXG5cdFx0YXdhaXQgdGhpcy5sb2FkU2V0dGluZ3MoKTtcclxuXHJcblx0XHR0aGlzLmRhdGVVdGlscyA9IG5ldyBEYXRlVXRpbHMoKTtcclxuXHRcdHRoaXMuZGF0ZVJhbmdlRXhwYW5kZXIgPSBuZXcgRGF0ZVJhbmdlRXhwYW5kZXIodGhpcy5hcHAsIHRoaXMuc2V0dGluZ3MsIHRoaXMuZGF0ZVV0aWxzKTtcclxuXHJcblx0XHR0aGlzLmFkZFNldHRpbmdUYWIobmV3IERhdGVSYW5nZUV4cGFuZGVyU2V0dGluZ1RhYih0aGlzLmFwcCwgdGhpcykpO1xyXG5cclxuXHRcdHRoaXMuYWRkQ29tbWFuZCh7XHJcblx0XHRcdGlkOiAnaW5zZXJ0LWV4cGFuZGVkLWRhdGUtcmFuZ2UnLFxyXG5cdFx0XHRuYW1lOiAnSW5zZXJ0JyxcclxuXHRcdFx0Y2hlY2tDYWxsYmFjazogKGNoZWNraW5nOiBib29sZWFuKSA9PiB7XHJcblx0XHRcdFx0Y29uc3QgbWFya2Rvd25WaWV3ID0gdGhpcy5hcHAud29ya3NwYWNlLmdldEFjdGl2ZVZpZXdPZlR5cGUoTWFya2Rvd25WaWV3KTtcclxuXHRcdFx0XHRpZiAobWFya2Rvd25WaWV3KSB7XHJcblx0XHRcdFx0XHRpZiAoIWNoZWNraW5nKSB7XHJcblx0XHRcdFx0XHRcdG5ldyBEYXRlSW5wdXRNb2RhbCh0aGlzLmFwcCwgKHJhbmdlSW5wdXQpID0+IHtcclxuXHRcdFx0XHRcdFx0XHRjb25zdCBkYXRlU3RhcnRBbmRFbmQgPSB0aGlzLmRhdGVVdGlscy5nZXRTdGFydEFuZEVuZERhdGVzKHJhbmdlSW5wdXQpO1xyXG5cclxuXHRcdFx0XHRcdFx0XHRpZiAoZGF0ZVN0YXJ0QW5kRW5kKSB7XHJcblx0XHRcdFx0XHRcdFx0XHRjb25zdCBlZGl0b3IgPSB0aGlzLmdldEFjdGl2ZUVkaXRvcigpO1xyXG5cdFx0XHRcdFx0XHRcdFx0aWYgKGVkaXRvcikge1xyXG5cdFx0XHRcdFx0XHRcdFx0XHRjb25zdCBjdXJzb3IgPSBlZGl0b3IuZ2V0Q3Vyc29yKCk7XHJcblx0XHRcdFx0XHRcdFx0XHRcdGxldCBpbnNlcnRlZERhdGVSYW5nZSA9IHRoaXMuZGF0ZVJhbmdlRXhwYW5kZXIuZXhwYW5kRGF0ZVJhbmdlKGRhdGVTdGFydEFuZEVuZCkgKyAnICc7XHJcblx0XHRcdFx0XHRcdFx0XHRcdGlmIChyYW5nZUlucHV0LnVzZUNhbGxvdXQpIHtcclxuXHRcdFx0XHRcdFx0XHRcdFx0XHRpbnNlcnRlZERhdGVSYW5nZSA9IHRoaXMuZGF0ZVJhbmdlRXhwYW5kZXIud3JhcEluQ2FsbG91dChkYXRlU3RhcnRBbmRFbmQsIGluc2VydGVkRGF0ZVJhbmdlKTtcclxuXHRcdFx0XHRcdFx0XHRcdFx0fVxyXG5cdFx0XHRcdFx0XHRcdFx0XHRlZGl0b3IucmVwbGFjZVJhbmdlKGluc2VydGVkRGF0ZVJhbmdlLCB7IGxpbmU6IGN1cnNvci5saW5lLCBjaDogY3Vyc29yLmNoIH0sIGN1cnNvcik7XHJcblxyXG5cdFx0XHRcdFx0XHRcdFx0XHRjb25zdCBlbmRQb3MgPSB7IGxpbmU6IGN1cnNvci5saW5lLCBjaDogY3Vyc29yLmNoICsgaW5zZXJ0ZWREYXRlUmFuZ2UubGVuZ3RoIH07XHJcblx0XHRcdFx0XHRcdFx0XHRcdGVkaXRvci5zZXRDdXJzb3IoZW5kUG9zKTtcclxuXHRcdFx0XHRcdFx0XHRcdH1cclxuXHRcdFx0XHRcdFx0XHR9XHJcblx0XHRcdFx0XHRcdH0pLm9wZW4oKTtcclxuXHRcdFx0XHRcdH1cclxuXHJcblx0XHRcdFx0XHRyZXR1cm4gdHJ1ZTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHRcdH0pO1xyXG5cdH1cclxuXHJcblx0YXN5bmMgbG9hZFNldHRpbmdzKCkgeyB0aGlzLnNldHRpbmdzID0gT2JqZWN0LmFzc2lnbih7fSwgREVGQVVMVF9TRVRUSU5HUywgYXdhaXQgdGhpcy5sb2FkRGF0YSgpKTsgfVxyXG5cclxuXHRhc3luYyBzYXZlU2V0dGluZ3MoKSB7IGF3YWl0IHRoaXMuc2F2ZURhdGEodGhpcy5zZXR0aW5ncyk7IH1cclxuXHJcblx0b251bmxvYWQoKSB7XHJcbiAgICAgICAgY29uc29sZS5sb2coJ0RhdGVSYW5nZUV4cGFuZGVyIHVubG9hZGVkJyk7XHJcbiAgICB9XHJcblxyXG5cdGdldEFjdGl2ZUVkaXRvcigpIHtcclxuXHRcdGNvbnN0IGFjdGl2ZUxlYWYgPSB0aGlzLmFwcC53b3Jrc3BhY2UuZ2V0QWN0aXZlVmlld09mVHlwZShNYXJrZG93blZpZXcpO1xyXG5cdFx0aWYgKGFjdGl2ZUxlYWYpIHtcclxuXHRcdFx0cmV0dXJuIGFjdGl2ZUxlYWYuZWRpdG9yO1xyXG5cdFx0fVxyXG5cdFx0cmV0dXJuIG51bGw7XHJcblx0fVxyXG59IiwgImltcG9ydCB7IEFwcCwgTW9kYWwsIFNldHRpbmcgfSBmcm9tICdvYnNpZGlhbic7XHJcbmltcG9ydCB7IERhdGVJbnB1dE1vZGFsUmVzcG9uc2UgfSBmcm9tICcuL3R5cGVzJztcclxuaW1wb3J0IHsgRGF0ZVV0aWxzIH0gZnJvbSAnLi9kYXRlVXRpbHMnO1xyXG5cclxuZXhwb3J0IGNsYXNzIERhdGVJbnB1dE1vZGFsIGV4dGVuZHMgT
|