You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1091 lines
182 KiB

3 years ago
'use strict';
var obsidian = require('obsidian');
3 years ago
/******************************************************************************
3 years ago
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
2 years ago
while (g && (g = 0, op[0] && (_ = 0)), _) try {
3 years ago
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
/** @deprecated */
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}
var DeleteFilesModal = /** @class */ (function (_super) {
__extends(DeleteFilesModal, _super);
function DeleteFilesModal(app, filesToDelete) {
var _this = _super.call(this, app) || this;
_this.filesToDelete = filesToDelete;
return _this;
}
DeleteFilesModal.prototype.onOpen = function () {
var _this = this;
var _a = this, contentEl = _a.contentEl, titleEl = _a.titleEl;
2 years ago
titleEl.setText("Move " + this.filesToDelete.length + " files to system trash?");
3 years ago
contentEl
.createEl("button", { text: "Cancel" })
.addEventListener("click", function () { return _this.close(); });
2 years ago
contentEl.setAttr("margin", "auto");
3 years ago
contentEl
.createEl("button", {
cls: "mod-cta",
2 years ago
text: "Confirm",
3 years ago
})
.addEventListener("click", function () { return __awaiter(_this, void 0, void 0, function () {
var _i, _a, file;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_i = 0, _a = this.filesToDelete;
_b.label = 1;
case 1:
if (!(_i < _a.length)) return [3 /*break*/, 4];
file = _a[_i];
return [4 /*yield*/, this.app.vault.trash(file, true)];
case 2:
_b.sent();
_b.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
this.close();
return [2 /*return*/];
}
});
}); });
};
DeleteFilesModal.prototype.onClose = function () {
var contentEl = this.contentEl;
contentEl.empty();
};
return DeleteFilesModal;
}(obsidian.Modal));
var SettingsTab = /** @class */ (function (_super) {
__extends(SettingsTab, _super);
function SettingsTab(app, plugin, defaultSettings) {
var _this = _super.call(this, app, plugin) || this;
_this.defaultSettings = defaultSettings;
_this.plugin = plugin;
return _this;
}
3 years ago
// Add trailing slash to catch files named like the directory. See https://github.com/Vinzent03/find-unlinked-files/issues/24
3 years ago
SettingsTab.prototype.formatPath = function (path, addDirectorySlash) {
if (path.length == 0)
return path;
path = obsidian.normalizePath(path);
if (addDirectorySlash)
return path + "/";
else
return path;
};
SettingsTab.prototype.display = function () {
var _this = this;
var containerEl = this.containerEl;
containerEl.empty();
containerEl.createEl("h2", { text: this.plugin.manifest.name });
2 years ago
containerEl.createEl("h4", {
text: "Settings for finding orphaned files",
});
new obsidian.Setting(containerEl).setName("Open output file").addToggle(function (cb) {
return cb
.setValue(_this.plugin.settings.openOutputFile)
2 years ago
.onChange(function (value) {
_this.plugin.settings.openOutputFile = value;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Output file name")
.setDesc("Set name of output file (without file extension). Make sure no file exists with this name because it will be overwritten! If the name is empty, the default name is set.")
.addText(function (cb) {
return cb
.onChange(function (value) {
if (value.length == 0) {
_this.plugin.settings.outputFileName =
_this.defaultSettings.outputFileName;
}
else {
_this.plugin.settings.outputFileName = value;
}
_this.plugin.saveSettings();
})
.setValue(_this.plugin.settings.outputFileName);
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Disable working links")
.setDesc("Indent lines to disable the link and to clean up the graph view")
.addToggle(function (cb) {
return cb
.onChange(function (value) {
_this.plugin.settings.disableWorkingLinks = value;
_this.plugin.saveSettings();
})
.setValue(_this.plugin.settings.disableWorkingLinks);
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude files in the given directories")
.setDesc("Enable to exclude files in the given directories. Disable to only include files in the given directories")
3 years ago
.addToggle(function (cb) {
2 years ago
return cb
.setValue(_this.plugin.settings.ignoreDirectories)
3 years ago
.onChange(function (value) {
_this.plugin.settings.ignoreDirectories = value;
_this.plugin.saveSettings();
});
});
new obsidian.Setting(containerEl)
.setName("Directories")
.setDesc("Add each directory path in a new line")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/Subdirectory")
.setValue(_this.plugin.settings.directoriesToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, true); });
_this.plugin.settings.directoriesToIgnore = paths;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude files")
3 years ago
.setDesc("Add each file path in a new line (with file extension!)")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/file.md")
.setValue(_this.plugin.settings.filesToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, false); });
_this.plugin.settings.filesToIgnore = paths;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude links")
.setDesc("Exclude files, which contain the given file as link. Add each file path in a new line (with file extension!). Set it to `*` to exclude files with links.")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/file.md")
.setValue(_this.plugin.settings.linksToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, false); });
_this.plugin.settings.linksToIgnore = paths;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude files with the given filetypes")
.setDesc("Enable to exclude files with the given filetypes. Disable to only include files with the given filetypes")
3 years ago
.addToggle(function (cb) {
2 years ago
return cb
.setValue(_this.plugin.settings.ignoreFileTypes)
3 years ago
.onChange(function (value) {
_this.plugin.settings.ignoreFileTypes = value;
_this.plugin.saveSettings();
});
});
new obsidian.Setting(containerEl)
.setName("File types")
.setDesc("Effect depends on toggle above")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("docx,txt")
.setValue(_this.plugin.settings.fileTypesToIgnore.join(","))
.onChange(function (value) {
var extensions = value.trim().split(",");
_this.plugin.settings.fileTypesToIgnore = extensions;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude tags")
.setDesc("Exclude files, which contain the given tag. Add each tag separated by comma (without `#`)")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("todo,unfinished")
.setValue(_this.plugin.settings.tagsToIgnore.join(","))
.onChange(function (value) {
var tags = value.trim().split(",");
_this.plugin.settings.tagsToIgnore = tags;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
.setName("Filetypes to delete per command. See README.")
.setDesc("Add each filetype separated by comma. Set to `*` to delete all files.")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("jpg,png")
.setValue(_this.plugin.settings.fileTypesToDelete.join(","))
.onChange(function (value) {
var extensions = value.trim().split(",");
_this.plugin.settings.fileTypesToDelete = extensions;
_this.plugin.saveSettings();
});
});
2 years ago
/// Settings for find brokenLinks
2 years ago
containerEl.createEl("h4", {
text: "Settings for finding broken links",
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Output file name")
.setDesc("Set name of output file (without file extension). Make sure no file exists with this name because it will be overwritten! If the name is empty, the default name is set.")
.addText(function (cb) {
return cb
.onChange(function (value) {
if (value.length == 0) {
_this.plugin.settings.unresolvedLinksOutputFileName =
_this.defaultSettings.unresolvedLinksOutputFileName;
}
else {
_this.plugin.settings.unresolvedLinksOutputFileName =
value;
}
_this.plugin.saveSettings();
})
.setValue(_this.plugin.settings.unresolvedLinksOutputFileName);
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude files in the given directories")
.setDesc("Enable to exclude files in the given directories. Disable to only include files in the given directories")
.addToggle(function (cb) {
2 years ago
return cb
.setValue(_this.plugin.settings.unresolvedLinksIgnoreDirectories)
2 years ago
.onChange(function (value) {
2 years ago
_this.plugin.settings.unresolvedLinksIgnoreDirectories =
value;
2 years ago
_this.plugin.saveSettings();
});
});
new obsidian.Setting(containerEl)
.setName("Directories")
.setDesc("Add each directory path in a new line")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/Subdirectory")
.setValue(_this.plugin.settings.unresolvedLinksDirectoriesToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, true); });
_this.plugin.settings.unresolvedLinksDirectoriesToIgnore =
paths;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude files")
.setDesc("Exclude links in the specified file. Add each file path in a new line (with file extension!)")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/file.md")
.setValue(_this.plugin.settings.unresolvedLinksFilesToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, false); });
_this.plugin.settings.unresolvedLinksFilesToIgnore =
paths;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude links")
.setDesc("Exclude files, which contain the given file as link. Add each file path in a new line (with file extension!). Set it to `*` to exclude files with links.")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/file.md")
.setValue(_this.plugin.settings.unresolvedLinksLinksToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, false); });
_this.plugin.settings.unresolvedLinksLinksToIgnore =
paths;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude filetypes")
.setDesc("Exclude links with the specified filetype. Add each filetype separated by comma")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("docx,txt")
.setValue(_this.plugin.settings.unresolvedLinksFileTypesToIgnore.join(","))
.onChange(function (value) {
var extensions = value.trim().split(",");
_this.plugin.settings.unresolvedLinksFileTypesToIgnore =
extensions;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude tags")
.setDesc("Exclude links in files, which contain the given tag. Add each tag separated by comma (without `#`)")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("todo,unfinished")
.setValue(_this.plugin.settings.unresolvedLinksTagsToIgnore.join(","))
.onChange(function (value) {
var tags = value.trim().split(",");
_this.plugin.settings.unresolvedLinksTagsToIgnore = tags;
_this.plugin.saveSettings();
});
});
containerEl.createEl("h4", {
text: "Settings for finding files without tags",
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Output file name")
.setDesc("Set name of output file (without file extension). Make sure no file exists with this name because it will be overwritten! If the name is empty, the default name is set.")
.addText(function (cb) {
return cb
.onChange(function (value) {
if (value.length == 0) {
_this.plugin.settings.withoutTagsOutputFileName =
_this.defaultSettings.withoutTagsOutputFileName;
}
else {
_this.plugin.settings.withoutTagsOutputFileName =
value;
}
_this.plugin.saveSettings();
})
.setValue(_this.plugin.settings.withoutTagsOutputFileName);
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude files")
.setDesc("Exclude the specific files. Add each file path in a new line (with file extension!)")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/file.md")
.setValue(_this.plugin.settings.withoutTagsFilesToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, false); });
_this.plugin.settings.withoutTagsFilesToIgnore = paths;
_this.plugin.saveSettings();
});
});
3 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Exclude directories")
.setDesc("Exclude files in the specified directories. Add each directory path in a new line")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/Subdirectory")
.setValue(_this.plugin.settings.withoutTagsDirectoriesToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, true); });
_this.plugin.settings.withoutTagsDirectoriesToIgnore =
paths;
_this.plugin.saveSettings();
});
});
2 years ago
/// Settings for empty files
2 years ago
containerEl.createEl("h4", {
text: "Settings for finding empty files",
});
new obsidian.Setting(containerEl)
.setName("Output file name")
.setDesc("Set name of output file (without file extension). Make sure no file exists with this name because it will be overwritten! If the name is empty, the default name is set.")
.addText(function (cb) {
return cb
.onChange(function (value) {
if (value.length == 0) {
_this.plugin.settings.emptyFilesOutputFileName =
_this.defaultSettings.emptyFilesOutputFileName;
}
else {
_this.plugin.settings.emptyFilesOutputFileName =
value;
}
_this.plugin.saveSettings();
})
.setValue(_this.plugin.settings.emptyFilesOutputFileName);
});
2 years ago
new obsidian.Setting(containerEl)
.setName("Exclude files in the given directories")
.setDesc("Enable to exclude files in the given directories. Disable to only include files in the given directories")
.addToggle(function (cb) {
2 years ago
return cb
.setValue(_this.plugin.settings.emptyFilesIgnoreDirectories)
2 years ago
.onChange(function (value) {
2 years ago
_this.plugin.settings.emptyFilesIgnoreDirectories =
value;
2 years ago
_this.plugin.saveSettings();
});
});
new obsidian.Setting(containerEl)
.setName("Directories")
.setDesc("Add each directory path in a new line")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/Subdirectory")
.setValue(_this.plugin.settings.emptyFilesDirectories.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, true); });
_this.plugin.settings.emptyFilesDirectories = paths;
_this.plugin.saveSettings();
});
});
2 years ago
new obsidian.Setting(containerEl)
.setName("Exclude files")
.setDesc("Add each file path in a new line (with file extension!)")
2 years ago
.addTextArea(function (cb) {
return cb
.setPlaceholder("Directory/file.md")
.setValue(_this.plugin.settings.emptyFilesFilesToIgnore.join("\n"))
.onChange(function (value) {
var paths = value
.trim()
.split("\n")
.map(function (value) { return _this.formatPath(value, false); });
_this.plugin.settings.emptyFilesFilesToIgnore = paths;
_this.plugin.saveSettings();
});
});
2 years ago
new obsidian.Setting(containerEl)
2 years ago
.setName("Donate")
.setDesc("If you like this Plugin, consider donating to support continued development.")
2 years ago
.addButton(function (bt) {
2 years ago
bt.buttonEl.outerHTML =
"<a href='https://ko-fi.com/F1F195IQ5' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi3.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>";
2 years ago
});
3 years ago
};
return SettingsTab;
}(obsidian.PluginSettingTab));
var Utils = /** @class */ (function () {
/**
2 years ago
* Checks for the given settings. Is used for `Find orphaned files` and `Find broken links`
3 years ago
* @param app
* @param filePath
* @param tagsToIgnore
* @param linksToIgnore
* @param directoriesToIgnore
* @param filesToIgnore
* @param ignoreDirectories
*/
function Utils(app, filePath, tagsToIgnore, linksToIgnore, directoriesToIgnore, filesToIgnore, ignoreDirectories, dir) {
3 years ago
if (ignoreDirectories === void 0) { ignoreDirectories = true; }
this.app = app;
this.filePath = filePath;
this.tagsToIgnore = tagsToIgnore;
this.linksToIgnore = linksToIgnore;
this.directoriesToIgnore = directoriesToIgnore;
this.filesToIgnore = filesToIgnore;
this.ignoreDirectories = ignoreDirectories;
this.dir = dir;
3 years ago
this.fileCache = app.metadataCache.getCache(filePath);
}
Utils.prototype.hasTagsToIgnore = function () {
var _this = this;
var tags = obsidian.getAllTags(this.fileCache);
2 years ago
return ((tags === null || tags === void 0 ? void 0 : tags.find(function (tag) {
return _this.tagsToIgnore.contains(tag.substring(1));
})) !== undefined);
3 years ago
};
Utils.prototype.hasLinksToIgnore = function () {
var _this = this;
var _a, _b;
2 years ago
if ((((_a = this.fileCache) === null || _a === void 0 ? void 0 : _a.embeds) != null || ((_b = this.fileCache) === null || _b === void 0 ? void 0 : _b.links) != null) &&
this.linksToIgnore[0] == "*") {
3 years ago
return true;
}
return obsidian.iterateCacheRefs(this.fileCache, function (cb) {
var _a;
var link = (_a = _this.app.metadataCache.getFirstLinkpathDest(cb.link, _this.filePath)) === null || _a === void 0 ? void 0 : _a.path;
return _this.linksToIgnore.contains(link);
});
};
Utils.prototype.checkDirectory = function () {
var _this = this;
if (this.dir) {
if (!this.filePath.startsWith(this.dir)) {
return true;
}
}
var contains = this.directoriesToIgnore.find(function (value) { return value.length != 0 && _this.filePath.startsWith(value); }) !== undefined;
3 years ago
if (this.ignoreDirectories) {
return contains;
}
else {
return !contains;
}
};
Utils.prototype.isFileToIgnore = function () {
return this.filesToIgnore.contains(this.filePath);
};
Utils.prototype.isValid = function () {
2 years ago
return (!this.hasTagsToIgnore() &&
!this.hasLinksToIgnore() &&
!this.checkDirectory() &&
!this.isFileToIgnore());
3 years ago
};
/**
* Writes the text to the file and opens the file in a new pane if it is not opened yet
* @param app
* @param outputFileName name of the output file
* @param text data to be written to the file
*/
2 years ago
Utils.writeAndOpenFile = function (app, outputFileName, text, openFile) {
3 years ago
return __awaiter(this, void 0, void 0, function () {
var fileIsAlreadyOpened, newPane, file;
3 years ago
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, app.vault.adapter.write(outputFileName, text)];
case 1:
_a.sent();
2 years ago
if (!openFile)
return [2 /*return*/];
3 years ago
fileIsAlreadyOpened = false;
app.workspace.iterateAllLeaves(function (leaf) {
2 years ago
if (leaf.getDisplayText() != "" &&
outputFileName.startsWith(leaf.getDisplayText())) {
3 years ago
fileIsAlreadyOpened = true;
}
});
if (!!fileIsAlreadyOpened) return [3 /*break*/, 5];
newPane = app.workspace.getLeavesOfType("empty").length == 0;
if (!newPane) return [3 /*break*/, 2];
app.workspace.openLinkText(outputFileName, "/", true);
return [3 /*break*/, 5];
case 2:
file = app.vault.getAbstractFileByPath(outputFileName);
if (!(file instanceof obsidian.TFile)) return [3 /*break*/, 4];
2 years ago
return [4 /*yield*/, app.workspace
.getLeavesOfType("empty")[0]
.openFile(file)];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
app.workspace.openLinkText(outputFileName, "/", true);
_a.label = 5;
case 5: return [2 /*return*/];
3 years ago
}
});
});
};
return Utils;
}());
var DEFAULT_SETTINGS = {
2 years ago
outputFileName: "orphaned files output",
3 years ago
disableWorkingLinks: false,
directoriesToIgnore: [],
filesToIgnore: [],
fileTypesToIgnore: [],
linksToIgnore: [],
tagsToIgnore: [],
fileTypesToDelete: [],
ignoreFileTypes: true,
ignoreDirectories: true,
2 years ago
unresolvedLinksIgnoreDirectories: true,
2 years ago
unresolvedLinksOutputFileName: "broken links output",
3 years ago
unresolvedLinksDirectoriesToIgnore: [],
unresolvedLinksFilesToIgnore: [],
unresolvedLinksFileTypesToIgnore: [],
unresolvedLinksLinksToIgnore: [],
unresolvedLinksTagsToIgnore: [],
withoutTagsDirectoriesToIgnore: [],
withoutTagsFilesToIgnore: [],
2 years ago
withoutTagsOutputFileName: "files without tags",
2 years ago
emptyFilesOutputFileName: "empty files",
emptyFilesDirectories: [],
emptyFilesFilesToIgnore: [],
emptyFilesIgnoreDirectories: true,
2 years ago
openOutputFile: true,
3 years ago
};
2 years ago
var FindOrphanedFilesPlugin = /** @class */ (function (_super) {
__extends(FindOrphanedFilesPlugin, _super);
function FindOrphanedFilesPlugin() {
2 years ago
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.findExtensionRegex = /(\.[^.]+)$/;
return _this;
3 years ago
}
2 years ago
FindOrphanedFilesPlugin.prototype.onload = function () {
3 years ago
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
2 years ago
console.log("loading " + this.manifest.name + " plugin");
3 years ago
return [4 /*yield*/, this.loadSettings()];
case 1:
_a.sent();
this.addCommand({
2 years ago
id: "find-unlinked-files",
name: "Find orphaned files",
2 years ago
callback: function () { return _this.findOrphanedFiles(); },
3 years ago
});
this.addCommand({
2 years ago
id: "find-unresolved-link",
name: "Find broken links",
2 years ago
callback: function () { return _this.findBrokenLinks(); },
3 years ago
});
this.addCommand({
id: "delete-unlinked-files",
2 years ago
name: "Delete orphaned files with certain extension. See README",
2 years ago
callback: function () { return _this.deleteOrphanedFiles(); },
3 years ago
});
2 years ago
this.addCommand({
id: "create-files-of-broken-links",
name: "Create files of broken links",
2 years ago
callback: function () { return _this.createFilesOfBrokenLinks(); },
2 years ago
});
3 years ago
this.addCommand({
id: "find-files-without-tags",
name: "Find files without tags",
2 years ago
callback: function () { return _this.findFilesWithoutTags(); },
3 years ago
});
2 years ago
this.addCommand({
id: "find-empty-files",
name: "Find empty files",
2 years ago
callback: function () { return _this.findEmptyFiles(); },
2 years ago
});
this.addCommand({
id: "delete-empty-files",
name: "Delete empty files",
2 years ago
callback: function () { return _this.deleteEmptyFiles(); },
2 years ago
});
3 years ago
this.addSettingTab(new SettingsTab(this.app, this, DEFAULT_SETTINGS));
this.app.workspace.on("file-menu", function (menu, file, source, leaf) {
if (file instanceof obsidian.TFolder) {
menu.addItem(function (cb) {
cb.setIcon("search");
2 years ago
cb.setTitle("Find orphaned files");
3 years ago
// Add trailing slash to catch files named like the directory. See https://github.com/Vinzent03/find-unlinked-files/issues/24
2 years ago
cb.onClick(function (e) {
_this.findOrphanedFiles(file.path + "/");
});
});
}
});
3 years ago
return [2 /*return*/];
}
});
});
};
2 years ago
FindOrphanedFilesPlugin.prototype.createFilesOfBrokenLinks = function () {
var _a, _b;
return __awaiter(this, void 0, void 0, function () {
var links, filesToCreate, _i, links_1, link, file, foundType, _c, filesToCreate_1, file;
return __generator(this, function (_d) {
switch (_d.label) {
case 0: return [4 /*yield*/, this.app.vault.adapter.exists(this.settings.unresolvedLinksOutputFileName + ".md")];
case 1:
if (!(_d.sent())) {
new obsidian.Notice("Can't find file - Please run the `Find broken files' command before");
return [2 /*return*/];
}
links = (_a = this.app.metadataCache.getCache(this.settings.unresolvedLinksOutputFileName + ".md")) === null || _a === void 0 ? void 0 : _a.links;
if (!links) {
new obsidian.Notice("No broken links found");
return [2 /*return*/];
}
filesToCreate = [];
for (_i = 0, links_1 = links; _i < links_1.length; _i++) {
link = links_1[_i];
file = this.app.metadataCache.getFirstLinkpathDest(link.link, "/");
if (file)
continue;
foundType = (_b = this.findExtensionRegex.exec(link.link)) === null || _b === void 0 ? void 0 : _b[0];
if ((foundType !== null && foundType !== void 0 ? foundType : ".md") == ".md") {
if (foundType) {
filesToCreate.push(link.link);
}
else {
filesToCreate.push(link.link + ".md");
}
}
}
if (!filesToCreate) return [3 /*break*/, 5];
_c = 0, filesToCreate_1 = filesToCreate;
_d.label = 2;
case 2:
if (!(_c < filesToCreate_1.length)) return [3 /*break*/, 5];
file = filesToCreate_1[_c];
return [4 /*yield*/, this.app.vault.create(file, "")];
case 3:
_d.sent();
_d.label = 4;
case 4:
_c++;
return [3 /*break*/, 2];
case 5: return [2 /*return*/];
}
});
});
};
FindOrphanedFilesPlugin.prototype.findEmptyFiles = function () {
return __awaiter(this, void 0, void 0, function () {
var files, emptyFiles, _i, files_1, file, content, trimmedContent, cache, frontmatter, lines, prefix, text;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
files = this.app.vault.getFiles();
emptyFiles = [];
_i = 0, files_1 = files;
_a.label = 1;
case 1:
if (!(_i < files_1.length)) return [3 /*break*/, 4];
file = files_1[_i];
if (!new Utils(this.app, file.path, [], [], this.settings.emptyFilesDirectories, this.settings.emptyFilesFilesToIgnore, this.settings.emptyFilesIgnoreDirectories).isValid()) {
return [3 /*break*/, 3];
}
return [4 /*yield*/, this.app.vault.read(file)];
case 2:
content = _a.sent();
trimmedContent = content.trim();
if (!trimmedContent) {
emptyFiles.push(file);
}
cache = app.metadataCache.getFileCache(file);
frontmatter = cache === null || cache === void 0 ? void 0 : cache.frontmatter;
if (frontmatter) {
lines = content.trimRight().split("\n").length;
if (frontmatter.position.end.line == lines - 1) {
emptyFiles.push(file);
}
}
_a.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
if (this.settings.disableWorkingLinks)
prefix = " ";
else
prefix = "";
2 years ago
text = emptyFiles
.map(function (file) { return prefix + "- [[" + file.path + "]]"; })
.join("\n");
2 years ago
Utils.writeAndOpenFile(this.app, this.settings.emptyFilesOutputFileName + ".md", text, this.settings.openOutputFile);
return [2 /*return*/];
}
});
});
};
2 years ago
FindOrphanedFilesPlugin.prototype.findOrphanedFiles = function (dir) {
3 years ago
var _this = this;
var outFileName = this.settings.outputFileName + ".md";
var outFile;
var files = this.app.vault.getFiles();
var markdownFiles = this.app.vault.getMarkdownFiles();
var links = [];
markdownFiles.forEach(function (markFile) {
if (markFile.path == outFileName) {
outFile = markFile;
return;
}
obsidian.iterateCacheRefs(_this.app.metadataCache.getFileCache(markFile), function (cb) {
var txt = _this.app.metadataCache.getFirstLinkpathDest(obsidian.getLinkpath(cb.link), markFile.path);
if (txt != null)
links.push(txt.path);
});
});
2 years ago
var notLinkedFiles = files.filter(function (file) {
return _this.isValid(file, links, dir);
});
3 years ago
notLinkedFiles.remove(outFile);
var text = "";
var prefix;
if (this.settings.disableWorkingLinks)
prefix = " ";
else
prefix = "";
notLinkedFiles.sort(function (a, b) { return b.stat.size - a.stat.size; });
3 years ago
notLinkedFiles.forEach(function (file) {
2 years ago
text +=
prefix +
"- [[" +
_this.app.metadataCache.fileToLinktext(file, "/", false) +
"]]\n";
3 years ago
});
2 years ago
Utils.writeAndOpenFile(this.app, outFileName, text, this.settings.openOutputFile);
3 years ago
};
2 years ago
FindOrphanedFilesPlugin.prototype.deleteOrphanedFiles = function () {
3 years ago
var _a, _b;
return __awaiter(this, void 0, void 0, function () {
var links, filesToDelete;
var _this = this;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, this.app.vault.adapter.exists(this.settings.outputFileName + ".md")];
case 1:
if (!(_c.sent())) {
2 years ago
new obsidian.Notice("Can't find file - Please run the `Find orphaned files' command before");
3 years ago
return [2 /*return*/];
}
links = (_b = (_a = this.app.metadataCache.getCache(this.settings.outputFileName + ".md")) === null || _a === void 0 ? void 0 : _a.links) !== null && _b !== void 0 ? _b : [];
filesToDelete = [];
links.forEach(function (link) {
var file = _this.app.metadataCache.getFirstLinkpathDest(link.link, "/");
if (!file)
return;
2 years ago
if (_this.settings.fileTypesToDelete[0] == "*" ||
_this.settings.fileTypesToDelete.contains(file.extension)) {
3 years ago
filesToDelete.push(file);
}
});
if (filesToDelete.length > 0)
new DeleteFilesModal(this.app, filesToDelete).open();
return [2 /*return*/];
}
});
});
};
2 years ago
FindOrphanedFilesPlugin.prototype.deleteEmptyFiles = function () {
var _a, _b;
return __awaiter(this, void 0, void 0, function () {
var links, filesToDelete, _i, links_2, link, file;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, this.app.vault.adapter.exists(this.settings.emptyFilesOutputFileName + ".md")];
case 1:
if (!(_c.sent())) {
new obsidian.Notice("Can't find file - Please run the `Find orphaned files' command before");
return [2 /*return*/];
}
links = (_b = (_a = this.app.metadataCache.getCache(this.settings.emptyFilesOutputFileName + ".md")) === null || _a === void 0 ? void 0 : _a.links) !== null && _b !== void 0 ? _b : [];
filesToDelete = [];
for (_i = 0, links_2 = links; _i < links_2.length; _i++) {
link = links_2[_i];
file = this.app.metadataCache.getFirstLinkpathDest(link.link, "/");
if (!file)
return [2 /*return*/];
filesToDelete.push(file);
}
if (filesToDelete.length > 0)
new DeleteFilesModal(this.app, filesToDelete).open();
return [2 /*return*/];
}
});
});
};
2 years ago
FindOrphanedFilesPlugin.prototype.findBrokenLinks = function () {
3 years ago
var outFileName = this.settings.unresolvedLinksOutputFileName + ".md";
var links = [];
2 years ago
var brokenLinks = this.app.metadataCache.unresolvedLinks;
2 years ago
for (var sourceFilepath in brokenLinks) {
2 years ago
if (sourceFilepath ==
this.settings.unresolvedLinksOutputFileName + ".md")
3 years ago
continue;
2 years ago
var fileType = sourceFilepath.substring(sourceFilepath.lastIndexOf(".") + 1);
var utils = new Utils(this.app, sourceFilepath, this.settings.unresolvedLinksTagsToIgnore, this.settings.unresolvedLinksLinksToIgnore, this.settings.unresolvedLinksDirectoriesToIgnore, this.settings.unresolvedLinksFilesToIgnore, this.settings.unresolvedLinksIgnoreDirectories);
3 years ago
if (!utils.isValid())
continue;
var _loop_1 = function (link) {
var linkFileType = link.substring(link.lastIndexOf(".") + 1);
if (this_1.settings.unresolvedLinksFileTypesToIgnore.contains(linkFileType))
return "continue";
2 years ago
var formattedFilePath = sourceFilepath;
3 years ago
if (fileType == "md") {
2 years ago
formattedFilePath = sourceFilepath.substring(0, sourceFilepath.lastIndexOf(".md"));
3 years ago
}
2 years ago
var brokenLink = {
files: [formattedFilePath],
link: link,
};
2 years ago
if (links.contains(brokenLink))
3 years ago
return "continue";
var duplication = links.find(function (e) { return e.link == link; });
if (duplication) {
duplication.files.push(formattedFilePath);
}
else {
2 years ago
links.push(brokenLink);
3 years ago
}
};
var this_1 = this;
2 years ago
for (var link in brokenLinks[sourceFilepath]) {
3 years ago
_loop_1(link);
}
}
Utils.writeAndOpenFile(this.app, outFileName, __spreadArrays([
"Don't forget that creating the file from here may create the file in the wrong directory!"
2 years ago
], links.map(function (e) { return "- [[" + e.link + "]] in [[" + e.files.join("]], [[") + "]]"; })).join("\n"), this.settings.openOutputFile);
3 years ago
};
2 years ago
FindOrphanedFilesPlugin.prototype.findFilesWithoutTags = function () {
3 years ago
var _this = this;
var outFileName = this.settings.withoutTagsOutputFileName + ".md";
var outFile;
var files = this.app.vault.getMarkdownFiles();
var withoutFiles = files.filter(function (file) {
var _a;
if (new Utils(_this.app, file.path, [], [], _this.settings.withoutTagsDirectoriesToIgnore, _this.settings.withoutTagsFilesToIgnore, true).isValid()) {
2 years ago
return (((_a = obsidian.getAllTags(_this.app.metadataCache.getFileCache(file))
.length) !== null && _a !== void 0 ? _a : 0) <= 0);
3 years ago
}
else {
return false;
}
});
withoutFiles.remove(outFile);
var prefix;
if (this.settings.disableWorkingLinks)
prefix = " ";
else
prefix = "";
2 years ago
var text = withoutFiles
.map(function (file) { return prefix + "- [[" + file.path + "]]"; })
.join("\n");
2 years ago
Utils.writeAndOpenFile(this.app, outFileName, text, this.settings.openOutputFile);
3 years ago
};
/**
2 years ago
* Checks if the given file in an orphaned file
3 years ago
*
* @param file file to check
* @param links all links in the vault
*/
2 years ago
FindOrphanedFilesPlugin.prototype.isValid = function (file, links, dir) {
3 years ago
if (links.contains(file.path))
return false;
//filetypes to ignore by default
if (file.extension == "css")
return false;
if (this.settings.fileTypesToIgnore[0] !== "") {
var containsFileType = this.settings.fileTypesToIgnore.contains(file.extension);
if (this.settings.ignoreFileTypes) {
if (containsFileType)
return;
}
else {
if (!containsFileType)
return;
}
}
var utils = new Utils(this.app, file.path, this.settings.tagsToIgnore, this.settings.linksToIgnore, this.settings.directoriesToIgnore, this.settings.filesToIgnore, this.settings.ignoreDirectories, dir);
3 years ago
if (!utils.isValid())
return false;
return true;
};
2 years ago
FindOrphanedFilesPlugin.prototype.onunload = function () {
2 years ago
console.log("unloading " + this.manifest.name + " plugin");
3 years ago
};
2 years ago
FindOrphanedFilesPlugin.prototype.loadSettings = function () {
3 years ago
return __awaiter(this, void 0, void 0, function () {
var _a, _b, _c, _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
_a = this;
_c = (_b = Object).assign;
_d = [DEFAULT_SETTINGS];
return [4 /*yield*/, this.loadData()];
case 1:
_a.settings = _c.apply(_b, _d.concat([_e.sent()]));
return [2 /*return*/];
}
});
});
};
2 years ago
FindOrphanedFilesPlugin.prototype.saveSettings = function () {
3 years ago
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.saveData(this.settings)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
2 years ago
return FindOrphanedFilesPlugin;
3 years ago
}(obsidian.Plugin));
2 years ago
module.exports = FindOrphanedFilesPlugin;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9kZWxldGVGaWxlc01vZGFsLnRzIiwic3JjL3NldHRpbmdzVGFiLnRzIiwic3JjL3V0aWxzLnRzIiwic3JjL21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKlxyXG5Db3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi5cclxuXHJcblBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueVxyXG5wdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQuXHJcblxyXG5USEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUyBXSVRIXHJcblJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWVxyXG5BTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SIEFOWSBTUEVDSUFMLCBESVJFQ1QsXHJcbklORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMgV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTVxyXG5MT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUlxyXG5PVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SXHJcblBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuXHJcbioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqICovXHJcbi8qIGdsb2JhbCBSZWZsZWN0LCBQcm9taXNlICovXHJcblxyXG52YXIgZXh0ZW5kU3RhdGljcyA9IGZ1bmN0aW9uKGQsIGIpIHtcclxuICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHxcclxuICAgICAgICAoeyBfX3Byb3RvX186IFtdIH0gaW5zdGFuY2VvZiBBcnJheSAmJiBmdW5jdGlvbiAoZCwgYikgeyBkLl9fcHJvdG9fXyA9IGI7IH0pIHx8XHJcbiAgICAgICAgZnVuY3Rpb24gKGQsIGIpIHsgZm9yICh2YXIgcCBpbiBiKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGIsIHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXh0ZW5kcyhkLCBiKSB7XHJcbiAgICBpZiAodHlwZW9mIGIgIT09IFwiZnVuY3Rpb25cIiAmJiBiICE9PSBudWxsKVxyXG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDbGFzcyBleHRlbmRzIHZhbHVlIFwiICsgU3RyaW5nKGIpICsgXCIgaXMgbm90IGEgY29uc3RydWN0b3Igb3IgbnVsbFwiKTtcclxuICAgIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbiAgICBmdW5jdGlvbiBfXygpIHsgdGhpcy5jb25zdHJ1Y3RvciA9IGQ7IH1cclxuICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTtcclxufVxyXG5cclxuZXhwb3J0IHZhciBfX2Fzc2lnbiA9IGZ1bmN0aW9uKCkge1xyXG4gICAgX19hc3NpZ24gPSBPYmplY3QuYXNzaWduIHx8IGZ1bmN0aW9uIF9fYXNzaWduKHQpIHtcclxuICAgICAgICBmb3IgKHZhciBzLCBpID0gMSwgbiA9IGFyZ3VtZW50cy5sZW5ndGg7IGkgPCBuOyBpKyspIHtcclxuICAgICAgICAgICAgcyA9IGFyZ3VtZW50c1tpXTtcclxuICAgICAgICAgICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApKSB0W3BdID0gc1twXTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuIHQ7XHJcbiAgICB9XHJcbiAgICByZXR1cm4gX19hc3NpZ24uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fcmVzdChzLCBlKSB7XHJcbiAgICB2YXIgdCA9IHt9O1xyXG4gICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApICYmIGUuaW5kZXhPZihwKSA8IDApXHJcbiAgICAgICAgdFtwXSA9IHNbcF07XHJcbiAgICBpZiAocyAhPSBudWxsICYmIHR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzID09PSBcImZ1bmN0aW9uXCIpXHJcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIHAgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHMpOyBpIDwgcC5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICBpZiAoZS5pbmRleE9mKHBbaV0pIDwgMCAmJiBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwocywgcFtpXSkpXHJcbiAgICAgICAgICAgICAgICB0W3BbaV1dID0gc1twW2ldXTtcclxuICAgICAgICB9XHJcbiAgICByZXR1cm4gdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpIHtcclxuICAgIHZhciBjID0gYXJndW1lbnRzLmxlbmd0aCwgciA9IGMgPCAzID8gdGFyZ2V0IDogZGVzYyA9PT0gbnVsbCA/IGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKHRhcmdldCwga2V5KSA6IGRlc2MsIGQ7XHJcbiAgICBpZiAodHlwZW9mIFJlZmxlY3QgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIFJlZmxlY3QuZGVjb3JhdGUgPT09IFwiZnVuY3Rpb25cIikgciA9IFJlZmxlY3QuZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpO1xyXG4gICAgZWxzZSBmb3IgKHZhciBpID0gZGVjb3JhdG9ycy5