Files
pantosite/src/content/.obsidian/plugins/file-name-history/main.js

476 lines
65 KiB
JavaScript
Raw Normal View History

2026-04-11 00:41:28 +02:00
/*
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: () => FileNameHistoryPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian3 = require("obsidian");
// src/settings.ts
var DEFAULT_SETTINGS = {
historyPropertyName: "aliases",
ignoreRegexes: ["^_", "^Untitled$", "^Untitled \\d+$"],
timeoutSeconds: 5,
caseSensitive: false,
autoCreateFrontmatter: true,
includeFolders: [],
excludeFolders: [],
fileExtensions: ["md"],
trackFolderRenames: "",
excludePropertyName: ""
};
// src/ui/settings-tab.ts
var import_obsidian = require("obsidian");
var FileNameHistorySettingTab = class extends import_obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.icon = "lucide-forward";
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
const saveSettings = () => {
void this.plugin.saveSettings();
};
const generalGroup = new import_obsidian.SettingGroup(containerEl);
generalGroup.addSetting((setting) => {
setting.setName("History property name").setDesc("The list property to store file name history.").addText(
(text) => text.setPlaceholder("aliases").setValue(this.plugin.settings.historyPropertyName).onChange((value) => {
this.plugin.settings.historyPropertyName = value || "aliases";
saveSettings();
})
);
});
generalGroup.addSetting((setting) => {
setting.setName("Timeout seconds").setDesc("Time in seconds the name must be stable before adding to the configured property.").addSlider(
(slider) => slider.setLimits(1, 20, 1).setValue(this.plugin.settings.timeoutSeconds).setDynamicTooltip().onChange((value) => {
this.plugin.settings.timeoutSeconds = value;
saveSettings();
})
);
});
generalGroup.addSetting((setting) => {
setting.setName("Case-sensitive uniqueness").setDesc("If enabled, treat case differences as unique values in the configured property.").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.caseSensitive).onChange((value) => {
this.plugin.settings.caseSensitive = value;
saveSettings();
})
);
});
generalGroup.addSetting((setting) => {
setting.setName("Auto-create history property").setDesc("Automatically create the configured property if missing.").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.autoCreateFrontmatter).onChange((value) => {
this.plugin.settings.autoCreateFrontmatter = value;
saveSettings();
})
);
});
generalGroup.addSetting((setting) => {
setting.setName("File extensions").setDesc("Comma-separated list of file extensions to track.").addText(
(text) => text.setPlaceholder("Md, txt").setValue(this.plugin.settings.fileExtensions.join(",")).onChange((value) => {
this.plugin.settings.fileExtensions = value.split(",").map((s) => s.trim()).filter((s) => s);
saveSettings();
})
);
});
const filteringGroup = new import_obsidian.SettingGroup(containerEl).setHeading("Filtering");
const foldersGroup = new import_obsidian.SettingGroup(containerEl).setHeading("Folders");
const advancedGroup = new import_obsidian.SettingGroup(containerEl).setHeading("Advanced");
filteringGroup.addSetting((setting) => {
setting.setName("Ignore regex patterns").setDesc(
"Comma-separated regex patterns for file names or immediate parent folder names to ignore (e.g., ^_ for underscore prefixes, ^untitled$ for untitled). Leave empty to disable."
).addText(
(text) => text.setPlaceholder("^_, ^untitled$, ^untitled \\d+$").setValue(this.plugin.settings.ignoreRegexes.join(",")).onChange((value) => {
this.plugin.settings.ignoreRegexes = value.split(",").map((s) => s.trim()).filter((s) => s);
saveSettings();
})
);
});
filteringGroup.addSetting((setting) => {
setting.setName("Exclude property name").setDesc(
"Name of a boolean property to check in files. Files with this property set to true will be excluded from tracking. Takes priority over folder filtering."
).addText(
(text) => text.setPlaceholder("Skip-rename-tracking").setValue(this.plugin.settings.excludePropertyName).onChange((value) => {
this.plugin.settings.excludePropertyName = value;
saveSettings();
})
);
});
foldersGroup.addSetting((setting) => {
setting.setName("Include folders").setDesc(
"Comma-separated list of folder paths to include. If empty, all folders are included. Use {vault} or {root} to include only files directly in the vault root (no subfolders)."
).addText(
(text) => text.setValue(this.plugin.settings.includeFolders.join(",")).onChange((value) => {
this.plugin.settings.includeFolders = value.split(",").map((s) => s.trim()).filter((s) => s);
saveSettings();
})
);
});
foldersGroup.addSetting((setting) => {
setting.setName("Exclude folders").setDesc(
'Comma-separated list of folder paths to exclude. Supports wildcards: use "folder/*" to exclude direct children, "folder/**" to exclude all descendants. Use {vault} or {root} to exclude files directly in the vault root.'
).addText(
(text) => text.setValue(this.plugin.settings.excludeFolders.join(",")).onChange((value) => {
this.plugin.settings.excludeFolders = value.split(",").map((s) => s.trim()).filter((s) => s);
saveSettings();
})
);
});
advancedGroup.addSetting((setting) => {
setting.setName("Track folder renames for specific file name").setDesc(
"If a Markdown file matches this file name, store old immediate parent folder names in the configured property when parent folders are renamed."
).addText(
(text) => text.setPlaceholder("Index").setValue(this.plugin.settings.trackFolderRenames).onChange((value) => {
this.plugin.settings.trackFolderRenames = value;
saveSettings();
})
);
});
}
};
// src/utils/history-processor.ts
var import_obsidian2 = require("obsidian");
var HistoryProcessor = class {
constructor(app, settings) {
this.app = app;
this.settings = settings;
}
async processAliasesManually(path, queue) {
const file = this.app.vault.getFileByPath(path);
if (!file) {
return;
}
const regexes = [];
for (const regexStr of this.settings.ignoreRegexes) {
try {
regexes.push(new RegExp(regexStr));
} catch (e) {
console.error(`Invalid ignore regex: ${regexStr}`, e);
}
}
const toAdd = [];
const currentBasename = file.basename;
const currentBasenameLower = currentBasename.toLowerCase();
for (const name of queue) {
if (regexes.some((re) => re.test(name))) {
continue;
}
const nameLower = name.toLowerCase();
if (this.settings.caseSensitive && name === currentBasename || !this.settings.caseSensitive && nameLower === currentBasenameLower) {
continue;
}
toAdd.push(name);
}
if (toAdd.length === 0) {
return;
}
let content = await this.app.vault.read(file);
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
const match = content.match(frontmatterRegex);
let frontmatter = {};
let frontmatterText = "";
let bodyContent = content;
if (match) {
frontmatterText = match[1];
bodyContent = content.slice(match[0].length);
try {
const parsed = (0, import_obsidian2.parseYaml)(frontmatterText);
frontmatter = parsed && typeof parsed === "object" ? parsed : {};
} catch (e) {
console.error(`Error parsing properties:`, e);
frontmatter = {};
}
} else {
bodyContent = content;
}
let aliases = frontmatter[this.settings.historyPropertyName];
if (!Array.isArray(aliases)) {
const hasFrontmatter = Object.keys(frontmatter).length > 0;
if (hasFrontmatter && !this.settings.autoCreateFrontmatter) {
return;
}
aliases = [];
}
const aliasesArray = aliases;
const existing = new Set(
this.settings.caseSensitive ? aliasesArray : aliasesArray.map((a) => a.toLowerCase())
);
let added = false;
for (const name of toAdd) {
const checkName = this.settings.caseSensitive ? name : name.toLowerCase();
if (!existing.has(checkName)) {
aliasesArray.push(name);
existing.add(checkName);
added = true;
}
}
if (!added) {
return;
}
frontmatter[this.settings.historyPropertyName] = aliasesArray;
const newFrontmatterText = (0, import_obsidian2.stringifyYaml)(frontmatter).trim();
const newContent = `---
${newFrontmatterText}
---
${bodyContent}`;
await this.app.vault.modify(file, newContent);
}
async processAliases(path, queue) {
const file = this.app.vault.getFileByPath(path);
if (!file) return;
const regexes = [];
for (const regexStr of this.settings.ignoreRegexes) {
try {
regexes.push(new RegExp(regexStr));
} catch (e) {
console.error(`Invalid ignore regex: ${regexStr}`, e);
}
}
const toAdd = [];
const currentBasename = file.basename;
const currentBasenameLower = currentBasename.toLowerCase();
for (const name of queue) {
if (regexes.some((re) => re.test(name))) {
continue;
}
const nameLower = name.toLowerCase();
if (this.settings.caseSensitive && name === currentBasename || !this.settings.caseSensitive && nameLower === currentBasenameLower) {
continue;
}
toAdd.push(name);
}
if (toAdd.length === 0) {
return;
}
if (file.extension !== "md") {
await this.processAliasesManually(path, queue);
return;
}
await this.app.fileManager.processFrontMatter(file, (fm) => {
let aliases = fm[this.settings.historyPropertyName];
if (!Array.isArray(aliases)) {
const hasFrontmatter = Object.keys(fm).length > 0;
if (hasFrontmatter && !this.settings.autoCreateFrontmatter) {
return;
}
aliases = [];
fm[this.settings.historyPropertyName] = aliases;
}
const aliasesArray = aliases;
const existing = new Set(
this.settings.caseSensitive ? aliasesArray : aliasesArray.map((a) => a.toLowerCase())
);
for (const name of toAdd) {
const checkName = this.settings.caseSensitive ? name : name.toLowerCase();
if (!existing.has(checkName)) {
aliasesArray.push(name);
existing.add(checkName);
}
}
});
}
};
// src/utils/path-utils.ts
function getBasename(path) {
const name = path.split("/").pop() || "";
return name.replace(/\.[^/.]+$/, "");
}
function getImmediateParentName(path) {
const parts = path.split("/");
parts.pop();
return parts.pop() || "";
}
// src/main.ts
var FileNameHistoryPlugin = class extends import_obsidian3.Plugin {
constructor() {
super(...arguments);
this.debounceMap = /* @__PURE__ */ new Map();
}
async onload() {
await this.loadSettings();
this.historyProcessor = new HistoryProcessor(this.app, this.settings);
this.addSettingTab(new FileNameHistorySettingTab(this.app, this));
this.registerEvent(
this.app.vault.on("rename", (file, oldPath) => {
this.handleRename(file, oldPath);
})
);
}
onunload() {
for (const entry of this.debounceMap.values()) {
if (entry.timeoutId !== 0) {
window.clearTimeout(entry.timeoutId);
}
}
this.debounceMap.clear();
}
async loadSettings() {
const loadedData = await this.loadData();
this.settings = Object.assign({}, DEFAULT_SETTINGS, loadedData);
}
async saveSettings() {
await this.saveData(this.settings);
}
isPathInFolder(path, folder) {
if (folder.includes("{vault}") || folder.includes("{root}")) {
const resolvedFolder = folder.replace(/\{vault\}|\{root\}/g, "");
if (resolvedFolder === "" || resolvedFolder === "/") {
const isVaultRoot = !path.includes("/");
return isVaultRoot;
}
return path.startsWith(resolvedFolder + "/") || path === resolvedFolder;
}
return path.startsWith(folder + "/") || path === folder;
}
isPathExcluded(path, excludePattern) {
if (excludePattern.includes("{vault}") || excludePattern.includes("{root}")) {
const resolvedPattern = excludePattern.replace(/\{vault\}|\{root\}/g, "");
if (resolvedPattern === "" || resolvedPattern === "/") {
return !path.includes("/");
}
excludePattern = resolvedPattern;
}
if (excludePattern.endsWith("/**")) {
const baseFolder = excludePattern.slice(0, -3);
return path.startsWith(baseFolder + "/") || path === baseFolder;
} else if (excludePattern.endsWith("/*")) {
const baseFolder = excludePattern.slice(0, -2);
if (!path.startsWith(baseFolder + "/")) {
return path === baseFolder;
}
const pathAfterBase = path.slice(baseFolder.length + 1);
return pathAfterBase.includes("/");
}
return path.startsWith(excludePattern + "/") || path === excludePattern;
}
handleRename(newFile, oldPath) {
if (!(newFile instanceof import_obsidian3.TFile)) return;
if (!this.settings.fileExtensions.includes(newFile.extension)) return;
const oldBasename = getBasename(oldPath);
const newBasename = newFile.basename;
const oldImmediateParentName = getImmediateParentName(oldPath);
const newImmediateParentName = getImmediateParentName(newFile.path);
const isNameChange = this.settings.caseSensitive ? oldBasename !== newBasename : oldBasename.toLowerCase() !== newBasename.toLowerCase();
const isFolderChange = oldImmediateParentName !== newImmediateParentName && !isNameChange;
if (!isNameChange && !isFolderChange) {
return;
}
const path = newFile.path;
if (this.settings.excludePropertyName && this.settings.excludePropertyName.trim() !== "") {
const cache = this.app.metadataCache.getFileCache(newFile);
const frontmatter = cache == null ? void 0 : cache.frontmatter;
if (frontmatter && frontmatter[this.settings.excludePropertyName] === true) {
return;
}
}
if (this.settings.includeFolders.length > 0) {
if (!this.settings.includeFolders.some((f) => this.isPathInFolder(path, f))) {
return;
}
}
const isIndexFileForFolderRename = isFolderChange && this.settings.trackFolderRenames && this.settings.trackFolderRenames.trim() !== "" && (this.settings.caseSensitive ? newFile.basename === this.settings.trackFolderRenames : newFile.basename.toLowerCase() === this.settings.trackFolderRenames.toLowerCase());
for (const excludePattern of this.settings.excludeFolders) {
if (this.isPathExcluded(path, excludePattern)) {
if (isIndexFileForFolderRename && excludePattern.endsWith("/*") && !excludePattern.endsWith("/**")) {
const baseFolder = excludePattern.slice(0, -2);
if (path.startsWith(baseFolder + "/")) {
const pathAfterBase = path.slice(baseFolder.length + 1);
const pathParts = pathAfterBase.split("/");
if (pathParts.length === 2) {
continue;
}
}
}
return;
}
}
const regexes = [];
for (const regexStr of this.settings.ignoreRegexes) {
try {
regexes.push(new RegExp(regexStr));
} catch (e) {
console.error(`Invalid ignore regex: ${regexStr}`, e);
}
}
let toQueue = null;
if (isNameChange) {
if (regexes.some((re) => re.test(oldBasename) || re.test(newBasename))) {
return;
}
toQueue = oldBasename;
} else if (isFolderChange && this.settings.trackFolderRenames && this.settings.trackFolderRenames.trim() !== "") {
const currentBasename = newFile.basename;
const matchesFilename = this.settings.caseSensitive ? currentBasename === this.settings.trackFolderRenames : currentBasename.toLowerCase() === this.settings.trackFolderRenames.toLowerCase();
if (!matchesFilename) {
return;
}
if (oldImmediateParentName === "" || newImmediateParentName === "") {
return;
}
if (regexes.some((re) => re.test(oldImmediateParentName) || re.test(newImmediateParentName))) {
return;
}
toQueue = oldImmediateParentName;
}
if (!toQueue) return;
let existingEntry = this.debounceMap.get(newFile.path);
if (!existingEntry) {
existingEntry = this.debounceMap.get(oldPath);
if (existingEntry) {
this.debounceMap.delete(oldPath);
}
}
if (existingEntry) {
if (existingEntry.timeoutId !== 0) {
window.clearTimeout(existingEntry.timeoutId);
}
toQueue = Array.from(existingEntry.queue)[0];
}
const entry = {
queue: /* @__PURE__ */ new Set([toQueue]),
timeoutId: 0,
currentPath: newFile.path
};
entry.timeoutId = window.setTimeout(() => {
void (async () => {
try {
await this.historyProcessor.processAliases(entry.currentPath, entry.queue);
} catch (error) {
console.error("Error processing aliases:", error);
}
this.debounceMap.delete(entry.currentPath);
})();
}, this.settings.timeoutSeconds * 1e3);
this.debounceMap.set(newFile.path, entry);
}
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3JjL21haW4udHMiLCAic3JjL3NldHRpbmdzLnRzIiwgInNyYy91aS9zZXR0aW5ncy10YWIudHMiLCAic3JjL3V0aWxzL2hpc3RvcnktcHJvY2Vzc29yLnRzIiwgInNyYy91dGlscy9wYXRoLXV0aWxzLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJpbXBvcnQgeyBQbHVnaW4sIFRBYnN0cmFjdEZpbGUsIFRGaWxlIH0gZnJvbSAnb2JzaWRpYW4nO1xuaW1wb3J0IHsgRmlsZU5hbWVIaXN0b3J5U2V0dGluZ3MsIERFRkFVTFRfU0VUVElOR1MgfSBmcm9tICcuL3NldHRpbmdzJztcbmltcG9ydCB7IEZpbGVOYW1lSGlzdG9yeVNldHRpbmdUYWIgfSBmcm9tICcuL3VpL3NldHRpbmdzLXRhYic7XG5pbXBvcnQgeyBIaXN0b3J5UHJvY2Vzc29yIH0gZnJvbSAnLi91dGlscy9oaXN0b3J5LXByb2Nlc3Nvcic7XG5pbXBvcnQgeyBnZXRCYXNlbmFtZSwgZ2V0SW1tZWRpYXRlUGFyZW50TmFtZSB9IGZyb20gJy4vdXRpbHMvcGF0aC11dGlscyc7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEZpbGVOYW1lSGlzdG9yeVBsdWdpbiBleHRlbmRzIFBsdWdpbiB7XG4gIHNldHRpbmdzOiBGaWxlTmFtZUhpc3RvcnlTZXR0aW5ncztcbiAgcHJpdmF0ZSBkZWJvdW5jZU1hcDogTWFwPHN0cmluZywgeyBxdWV1ZTogU2V0PHN0cmluZz47IHRpbWVvdXRJZDogbnVtYmVyOyBjdXJyZW50UGF0aDogc3RyaW5nIH0+ID0gbmV3IE1hcCgpO1xuICBwcml2YXRlIGhpc3RvcnlQcm9jZXNzb3I6IEhpc3RvcnlQcm9jZXNzb3I7XG5cbiAgYXN5bmMgb25sb2FkKCkge1xuICAgIGF3YWl0IHRoaXMubG9hZFNldHRpbmdzKCk7XG4gICAgdGhpcy5oaXN0b3J5UHJvY2Vzc29yID0gbmV3IEhpc3RvcnlQcm9jZXNzb3IodGhpcy5hcHAsIHRoaXMuc2V0dGluZ3MpO1xuICAgIHRoaXMuYWRkU2V0dGluZ1RhYihuZXcgRmlsZU5hbWVIaXN0b3J5U2V0dGluZ1RhYih0aGlzLmFwcCwgdGhpcykpO1xuICAgIHRoaXMucmVnaXN0ZXJFdmVudChcbiAgICAgIHRoaXMuYXBwLnZhdWx0Lm9uKCdyZW5hbWUnLCAoZmlsZTogVEFic3RyYWN0RmlsZSwgb2xkUGF0aDogc3RyaW5nKSA9PiB7XG4gICAgICAgIHRoaXMuaGFuZGxlUmVuYW1lKGZpbGUsIG9sZFBhdGgpO1xuICAgICAgfSlcbiAgICApO1xuICB9XG5cbiAgb251bmxvYWQoKSB7XG4gICAgLy8gQ2xlYXIgYW55IHBlbmRpbmcgdGltZW91dHNcbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIHRoaXMuZGVib3VuY2VNYXAudmFsdWVzKCkpIHtcbiAgICAgIGlmIChlbnRyeS50aW1lb3V0SWQgIT09IDApIHtcbiAgICAgICAgd2luZG93LmNsZWFyVGltZW91dChlbnRyeS50aW1lb3V0SWQpO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLmRlYm91bmNlTWFwLmNsZWFyKCk7XG4gIH1cblxuICBhc3luYyBsb2FkU2V0dGluZ3MoKSB7XG4gICAgY29uc3QgbG9hZGVkRGF0YSA9IChhd2FpdCB0aGlzLmxvYWREYXRhKCkpIGFzIFBhcnRpYWw8RmlsZU5hbWVIaXN0b3J5U2V0dGluZ3M+IHwgbnVsbDtcbiAgICB0aGlzLnNldHRpbmdzID0gT2JqZWN0LmFzc2lnbih7fSwgREVGQVVMVF9TRVRUSU5HUywgbG9hZGVkRGF0YSk7XG4gIH1cblxuICBhc3luYyBzYXZlU2V0dGluZ3MoKSB7XG4gICAgYXdhaXQgdGhpcy5zYXZlRGF0YSh0aGlzLnNldHRpbmdzKTtcbiAgfVxuXG4gIHByaXZhdGUgaXNQYXRoSW5Gb2xkZXIocGF0aDogc3RyaW5nLCBmb2xkZXI6IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgIC8vIEhhbmRsZSB2YXVsdCByb290IHZhcmlhYmxlXG4gICAgaWYgKGZvbGRlci5pbmNsdWRlcygne3ZhdWx0fScpIHx8IGZvbGRlci5pbmNsdWRlcygne3Jvb3R9JykpIHtcbiAgICAgIGNvbnN0IHJlc29sdmVkRm9sZGVyID0gZm9sZGVyLnJlcGxhY2UoL1xce3ZhdWx0XFx9fFxce3Jvb3RcXH0vZywgJycpO1xuICAgICAgLy8gSWYgdGhlIGZvbGRlciBpcyBqdXN0IHRoZSB2YXJpYWJsZSwgaXQgbWVhbnMgaW5jbHVkZSBvbmx5IHZhdWx0IHJvb3QgZmlsZXNcbiAgICAgIGlmIChyZXNvbHZlZEZvbGRlciA9PT0gJycgfHwgcmVzb2x2ZWRGb2xkZXIgPT09ICcvJykge1xuICAgICAgICAvLyBJbmNsdWRlIG9ubHkgZmlsZXMgZGlyZWN0bHkgaW4gdGhlIHZhdWx0IHJvb3QgKG5vIHN1YmZvbGRlcnMpXG4gICAgICAgIGNvbnN0IGlzVmF1bHRSb290ID0gIXBhdGguaW5jbHVkZXMoJy8nKTtcbiAgICAgICAgcmV0dXJuIGlzVmF1bHRSb290O1xuICAgICAgfVxuICAgICAgLy8gT3RoZXJ3aXNlLCByZXBsYWNlIHRoZSB2YXJpYWJsZSBhbmQgY2hlY2sgbm9ybWFsbHlcbiAgICAgIHJldHVybiBwYXRoLnN0YXJ0c1dpdGgocmVzb2x2ZWRGb2xkZXIgKyAnLycpIHx8IHBhdGggPT09IHJlc29sdmVkRm9sZGVyO1xuICAgIH1cblxuICAgIC8vIE5vcm1hbCBmb2xkZXIgbWF0Y2hpbmdcbiAgICByZXR1cm4gcGF0aC5zdGFydHNXaXRoKGZvbGRlciArICcvJykgfHwgcGF0aCA9PT0gZm9sZGVyO1xuICB9XG5cbiAgcHJpdmF0ZSBpc1BhdGhFeGNsdWRlZChwYXRoOiBzdHJpbmcsIGV4Y2x1ZGVQYXR0ZXJuOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICAvLyBIYW5kbGUgdmF1bHQgcm9vdCB2YXJpYWJsZVxuICAgIGlmIChleGNsdWRlUGF0dGVybi5pbmNsdWRlcygne3ZhdWx0fScpIHx8IGV4Y2x1ZGVQYXR0ZXJuLmluY2x1ZGVzKCd7cm9vdH0nKSkge1xuICAgICAgY29uc3QgcmVzb2x2ZWRQYXR0ZXJuID0gZXhjbHVkZVBhdHRlcm4ucmVwbGFjZSgvXFx7dmF1bHRcXH18XFx7cm9vdFxcfS9nLCAnJyk7XG4gICAgICBpZiAocmVzb2x2ZWRQYXR0ZXJuID09PSAnJyB8fCByZXNvbHZlZFBhdHRlcm4gPT09ICcvJykge1xuICAgICAgICAvLyBFeGNsdWRlIG9ubHkgZmlsZXMgZGlyZWN0bHkgaW4gdGhlIHZhdWx0IHJvb3QgKG5vIHN1YmZvbGRlcnMpXG4gICAgICAgIHJldHVybiAhcGF0aC5pbmNsdWRlcygnLycpO1xuICAgICAgfVxuICAgICAgZXhjbHVkZVBhdHRlcm4gPSByZXNvbHZlZFBhdHRlcm47XG4gICAgfVxuXG4gICAgLy8gSGFuZGxlIHdpbGRjYXJkc1xuICAgIGlmIChleGNsdWRlUGF0dGVybi5lbmRzV2l0aCgnL