mildeveloper_obsidian-date-.../main.js

643 lines
83 KiB
JavaScript
Raw Normal View History

/*
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