vault backup: 2025-03-04 23:30:01
This commit is contained in:
1
.obsidian/community-plugins.json
vendored
1
.obsidian/community-plugins.json
vendored
@@ -5,7 +5,6 @@
|
||||
"obsidian-columns",
|
||||
"obsidian-tasks-plugin",
|
||||
"oz-image-plugin",
|
||||
"periodic-notes",
|
||||
"obsidian-git",
|
||||
"obsidian-quiet-outline",
|
||||
"obsidian-tracker",
|
||||
|
||||
27
.obsidian/plugins/find-unlinked-files/data.json
vendored
27
.obsidian/plugins/find-unlinked-files/data.json
vendored
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"outputFileName": "orphaned files output",
|
||||
"disableWorkingLinks": false,
|
||||
"directoriesToIgnore": [],
|
||||
"filesToIgnore": [],
|
||||
"fileTypesToIgnore": [],
|
||||
"linksToIgnore": [],
|
||||
"tagsToIgnore": [],
|
||||
"fileTypesToDelete": [],
|
||||
"ignoreFileTypes": true,
|
||||
"ignoreDirectories": true,
|
||||
"unresolvedLinksIgnoreDirectories": true,
|
||||
"unresolvedLinksOutputFileName": "broken links output",
|
||||
"unresolvedLinksDirectoriesToIgnore": [],
|
||||
"unresolvedLinksFilesToIgnore": [],
|
||||
"unresolvedLinksFileTypesToIgnore": [],
|
||||
"unresolvedLinksLinksToIgnore": [],
|
||||
"unresolvedLinksTagsToIgnore": [],
|
||||
"withoutTagsDirectoriesToIgnore": [],
|
||||
"withoutTagsFilesToIgnore": [],
|
||||
"withoutTagsOutputFileName": "files without tags",
|
||||
"emptyFilesOutputFileName": "empty files",
|
||||
"emptyFilesDirectories": [],
|
||||
"emptyFilesFilesToIgnore": [],
|
||||
"emptyFilesIgnoreDirectories": true,
|
||||
"openOutputFile": true
|
||||
}
|
||||
859
.obsidian/plugins/find-unlinked-files/main.js
vendored
859
.obsidian/plugins/find-unlinked-files/main.js
vendored
@@ -1,859 +0,0 @@
|
||||
/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source visit the plugins github repository (https://github.com/Vinzent03/obsidian-advanced-uri)
|
||||
*/
|
||||
|
||||
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: () => FindOrphanedFilesPlugin
|
||||
});
|
||||
module.exports = __toCommonJS(main_exports);
|
||||
var import_obsidian4 = require("obsidian");
|
||||
|
||||
// src/deleteFilesModal.ts
|
||||
var import_obsidian = require("obsidian");
|
||||
var DeleteFilesModal = class extends import_obsidian.Modal {
|
||||
constructor(app, filesToDelete) {
|
||||
super(app);
|
||||
this.filesToDelete = filesToDelete;
|
||||
}
|
||||
onOpen() {
|
||||
let { contentEl, titleEl } = this;
|
||||
titleEl.setText(
|
||||
"Move " + this.filesToDelete.length + " files to system trash?"
|
||||
);
|
||||
contentEl.createEl("button", { text: "Cancel" }).addEventListener("click", () => this.close());
|
||||
contentEl.setAttr("margin", "auto");
|
||||
contentEl.createEl("button", {
|
||||
cls: "mod-cta",
|
||||
text: "Confirm"
|
||||
}).addEventListener("click", async () => {
|
||||
for (const file of this.filesToDelete) {
|
||||
await this.app.vault.trash(file, true);
|
||||
}
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
onClose() {
|
||||
let { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
};
|
||||
|
||||
// src/settingsTab.ts
|
||||
var import_obsidian2 = require("obsidian");
|
||||
var SettingsTab = class extends import_obsidian2.PluginSettingTab {
|
||||
constructor(app, plugin, defaultSettings) {
|
||||
super(app, plugin);
|
||||
this.defaultSettings = defaultSettings;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
// Add trailing slash to catch files named like the directory. See https://github.com/Vinzent03/find-unlinked-files/issues/24
|
||||
formatPath(path, addDirectorySlash) {
|
||||
if (path.length == 0)
|
||||
return path;
|
||||
path = (0, import_obsidian2.normalizePath)(path);
|
||||
if (addDirectorySlash)
|
||||
return path + "/";
|
||||
else
|
||||
return path;
|
||||
}
|
||||
display() {
|
||||
let { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl("h2", { text: this.plugin.manifest.name });
|
||||
containerEl.createEl("h4", {
|
||||
text: "Settings for finding orphaned files"
|
||||
});
|
||||
new import_obsidian2.Setting(containerEl).setName("Open output file").addToggle(
|
||||
(cb) => cb.setValue(this.plugin.settings.openOutputFile).onChange((value) => {
|
||||
this.plugin.settings.openOutputFile = value;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.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(
|
||||
(cb) => cb.onChange((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)
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Disable working links").setDesc(
|
||||
"Indent lines to disable the link and to clean up the graph view"
|
||||
).addToggle(
|
||||
(cb) => cb.onChange((value) => {
|
||||
this.plugin.settings.disableWorkingLinks = value;
|
||||
this.plugin.saveSettings();
|
||||
}).setValue(this.plugin.settings.disableWorkingLinks)
|
||||
);
|
||||
new import_obsidian2.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(
|
||||
(cb) => cb.setValue(this.plugin.settings.ignoreDirectories).onChange((value) => {
|
||||
this.plugin.settings.ignoreDirectories = value;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Directories").setDesc("Add each directory path in a new line").addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/Subdirectory").setValue(
|
||||
this.plugin.settings.directoriesToIgnore.join("\n")
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, true));
|
||||
this.plugin.settings.directoriesToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude files").setDesc("Add each file path in a new line (with file extension!)").addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/file.md").setValue(this.plugin.settings.filesToIgnore.join("\n")).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, false));
|
||||
this.plugin.settings.filesToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).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."
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/file.md").setValue(this.plugin.settings.linksToIgnore.join("\n")).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, false));
|
||||
this.plugin.settings.linksToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).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"
|
||||
).addToggle(
|
||||
(cb) => cb.setValue(this.plugin.settings.ignoreFileTypes).onChange((value) => {
|
||||
this.plugin.settings.ignoreFileTypes = value;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("File types").setDesc("Effect depends on toggle above").addTextArea(
|
||||
(cb) => cb.setPlaceholder("docx,txt").setValue(this.plugin.settings.fileTypesToIgnore.join(",")).onChange((value) => {
|
||||
let extensions = value.trim().split(",");
|
||||
this.plugin.settings.fileTypesToIgnore = extensions;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude tags").setDesc(
|
||||
"Exclude files, which contain the given tag. Add each tag separated by comma (without `#`)"
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("todo,unfinished").setValue(this.plugin.settings.tagsToIgnore.join(",")).onChange((value) => {
|
||||
let tags = value.trim().split(",");
|
||||
this.plugin.settings.tagsToIgnore = tags;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Filetypes to delete per command. See README.").setDesc(
|
||||
"Add each filetype separated by comma. Set to `*` to delete all files."
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("jpg,png").setValue(this.plugin.settings.fileTypesToDelete.join(",")).onChange((value) => {
|
||||
let extensions = value.trim().split(",");
|
||||
this.plugin.settings.fileTypesToDelete = extensions;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
containerEl.createEl("h4", {
|
||||
text: "Settings for finding broken links"
|
||||
});
|
||||
new import_obsidian2.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(
|
||||
(cb) => cb.onChange((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
|
||||
)
|
||||
);
|
||||
new import_obsidian2.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(
|
||||
(cb) => cb.setValue(
|
||||
this.plugin.settings.unresolvedLinksIgnoreDirectories
|
||||
).onChange((value) => {
|
||||
this.plugin.settings.unresolvedLinksIgnoreDirectories = value;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Directories").setDesc("Add each directory path in a new line").addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/Subdirectory").setValue(
|
||||
this.plugin.settings.unresolvedLinksDirectoriesToIgnore.join(
|
||||
"\n"
|
||||
)
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, true));
|
||||
this.plugin.settings.unresolvedLinksDirectoriesToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude files").setDesc(
|
||||
"Exclude links in the specified file. Add each file path in a new line (with file extension!)"
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/file.md").setValue(
|
||||
this.plugin.settings.unresolvedLinksFilesToIgnore.join(
|
||||
"\n"
|
||||
)
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, false));
|
||||
this.plugin.settings.unresolvedLinksFilesToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).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."
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/file.md").setValue(
|
||||
this.plugin.settings.unresolvedLinksLinksToIgnore.join(
|
||||
"\n"
|
||||
)
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, false));
|
||||
this.plugin.settings.unresolvedLinksLinksToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude filetypes").setDesc(
|
||||
"Exclude links with the specified filetype. Add each filetype separated by comma"
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("docx,txt").setValue(
|
||||
this.plugin.settings.unresolvedLinksFileTypesToIgnore.join(
|
||||
","
|
||||
)
|
||||
).onChange((value) => {
|
||||
let extensions = value.trim().split(",");
|
||||
this.plugin.settings.unresolvedLinksFileTypesToIgnore = extensions;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude tags").setDesc(
|
||||
"Exclude links in files, which contain the given tag. Add each tag separated by comma (without `#`)"
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("todo,unfinished").setValue(
|
||||
this.plugin.settings.unresolvedLinksTagsToIgnore.join(
|
||||
","
|
||||
)
|
||||
).onChange((value) => {
|
||||
let tags = value.trim().split(",");
|
||||
this.plugin.settings.unresolvedLinksTagsToIgnore = tags;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
containerEl.createEl("h4", {
|
||||
text: "Settings for finding files without tags"
|
||||
});
|
||||
new import_obsidian2.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(
|
||||
(cb) => cb.onChange((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)
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude files").setDesc(
|
||||
"Exclude the specific files. Add each file path in a new line (with file extension!)"
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/file.md").setValue(
|
||||
this.plugin.settings.withoutTagsFilesToIgnore.join("\n")
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, false));
|
||||
this.plugin.settings.withoutTagsFilesToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude directories").setDesc(
|
||||
"Exclude files in the specified directories. Add each directory path in a new line"
|
||||
).addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/Subdirectory").setValue(
|
||||
this.plugin.settings.withoutTagsDirectoriesToIgnore.join(
|
||||
"\n"
|
||||
)
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, true));
|
||||
this.plugin.settings.withoutTagsDirectoriesToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
containerEl.createEl("h4", {
|
||||
text: "Settings for finding empty files"
|
||||
});
|
||||
new import_obsidian2.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(
|
||||
(cb) => cb.onChange((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)
|
||||
);
|
||||
new import_obsidian2.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(
|
||||
(cb) => cb.setValue(this.plugin.settings.emptyFilesIgnoreDirectories).onChange((value) => {
|
||||
this.plugin.settings.emptyFilesIgnoreDirectories = value;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Directories").setDesc("Add each directory path in a new line").addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/Subdirectory").setValue(
|
||||
this.plugin.settings.emptyFilesDirectories.join("\n")
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, true));
|
||||
this.plugin.settings.emptyFilesDirectories = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude files").setDesc("Add each file path in a new line (with file extension!)").addTextArea(
|
||||
(cb) => cb.setPlaceholder("Directory/file.md").setValue(
|
||||
this.plugin.settings.emptyFilesFilesToIgnore.join("\n")
|
||||
).onChange((value) => {
|
||||
let paths = value.trim().split("\n").map((value2) => this.formatPath(value2, false));
|
||||
this.plugin.settings.emptyFilesFilesToIgnore = paths;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new import_obsidian2.Setting(containerEl).setName("Donate").setDesc(
|
||||
"If you like this Plugin, consider donating to support continued development."
|
||||
).addButton((bt) => {
|
||||
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>";
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// src/utils.ts
|
||||
var import_obsidian3 = require("obsidian");
|
||||
var Utils = class {
|
||||
/**
|
||||
* Checks for the given settings. Is used for `Find orphaned files` and `Find broken links`
|
||||
* @param app
|
||||
* @param filePath
|
||||
* @param tagsToIgnore
|
||||
* @param linksToIgnore
|
||||
* @param directoriesToIgnore
|
||||
* @param filesToIgnore
|
||||
* @param ignoreDirectories
|
||||
*/
|
||||
constructor(app, filePath, tagsToIgnore, linksToIgnore, directoriesToIgnore, filesToIgnore, ignoreDirectories = true, dir) {
|
||||
this.app = app;
|
||||
this.filePath = filePath;
|
||||
this.tagsToIgnore = tagsToIgnore;
|
||||
this.linksToIgnore = linksToIgnore;
|
||||
this.directoriesToIgnore = directoriesToIgnore;
|
||||
this.filesToIgnore = filesToIgnore;
|
||||
this.ignoreDirectories = ignoreDirectories;
|
||||
this.dir = dir;
|
||||
this.fileCache = app.metadataCache.getCache(filePath);
|
||||
}
|
||||
hasTagsToIgnore() {
|
||||
const tags = (0, import_obsidian3.getAllTags)(this.fileCache);
|
||||
return (tags == null ? void 0 : tags.find(
|
||||
(tag) => this.tagsToIgnore.contains(tag.substring(1))
|
||||
)) !== void 0;
|
||||
}
|
||||
hasLinksToIgnore() {
|
||||
var _a, _b;
|
||||
if ((((_a = this.fileCache) == null ? void 0 : _a.embeds) != null || ((_b = this.fileCache) == null ? void 0 : _b.links) != null) && this.linksToIgnore[0] == "*") {
|
||||
return true;
|
||||
}
|
||||
return (0, import_obsidian3.iterateCacheRefs)(this.fileCache, (cb) => {
|
||||
var _a2;
|
||||
const link = (_a2 = this.app.metadataCache.getFirstLinkpathDest(
|
||||
cb.link,
|
||||
this.filePath
|
||||
)) == null ? void 0 : _a2.path;
|
||||
return this.linksToIgnore.contains(link);
|
||||
});
|
||||
}
|
||||
checkDirectory() {
|
||||
if (this.dir) {
|
||||
if (!this.filePath.startsWith(this.dir)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const contains = this.directoriesToIgnore.find(
|
||||
(value) => value.length != 0 && this.filePath.startsWith(value)
|
||||
) !== void 0;
|
||||
if (this.ignoreDirectories) {
|
||||
return contains;
|
||||
} else {
|
||||
return !contains;
|
||||
}
|
||||
}
|
||||
isFileToIgnore() {
|
||||
return this.filesToIgnore.contains(this.filePath);
|
||||
}
|
||||
isValid() {
|
||||
return !this.hasTagsToIgnore() && !this.hasLinksToIgnore() && !this.checkDirectory() && !this.isFileToIgnore();
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static async writeAndOpenFile(app, outputFileName, text, openFile) {
|
||||
await app.vault.adapter.write(outputFileName, text);
|
||||
if (!openFile)
|
||||
return;
|
||||
let fileIsAlreadyOpened = false;
|
||||
app.workspace.iterateAllLeaves((leaf) => {
|
||||
if (leaf.getDisplayText() != "" && outputFileName.startsWith(leaf.getDisplayText())) {
|
||||
fileIsAlreadyOpened = true;
|
||||
}
|
||||
});
|
||||
if (!fileIsAlreadyOpened) {
|
||||
const newPane = app.workspace.getLeavesOfType("empty").length == 0;
|
||||
if (newPane) {
|
||||
app.workspace.openLinkText(outputFileName, "/", true);
|
||||
} else {
|
||||
const file = app.vault.getAbstractFileByPath(outputFileName);
|
||||
if (file instanceof import_obsidian3.TFile) {
|
||||
await app.workspace.getLeavesOfType("empty")[0].openFile(file);
|
||||
} else {
|
||||
app.workspace.openLinkText(outputFileName, "/", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// src/main.ts
|
||||
var DEFAULT_SETTINGS = {
|
||||
outputFileName: "orphaned files output",
|
||||
disableWorkingLinks: false,
|
||||
directoriesToIgnore: [],
|
||||
filesToIgnore: [],
|
||||
fileTypesToIgnore: [],
|
||||
linksToIgnore: [],
|
||||
tagsToIgnore: [],
|
||||
fileTypesToDelete: [],
|
||||
ignoreFileTypes: true,
|
||||
ignoreDirectories: true,
|
||||
unresolvedLinksIgnoreDirectories: true,
|
||||
unresolvedLinksOutputFileName: "broken links output",
|
||||
unresolvedLinksDirectoriesToIgnore: [],
|
||||
unresolvedLinksFilesToIgnore: [],
|
||||
unresolvedLinksFileTypesToIgnore: [],
|
||||
unresolvedLinksLinksToIgnore: [],
|
||||
unresolvedLinksTagsToIgnore: [],
|
||||
withoutTagsDirectoriesToIgnore: [],
|
||||
withoutTagsFilesToIgnore: [],
|
||||
withoutTagsOutputFileName: "files without tags",
|
||||
emptyFilesOutputFileName: "empty files",
|
||||
emptyFilesDirectories: [],
|
||||
emptyFilesFilesToIgnore: [],
|
||||
emptyFilesIgnoreDirectories: true,
|
||||
openOutputFile: true
|
||||
};
|
||||
var FindOrphanedFilesPlugin = class extends import_obsidian4.Plugin {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.findExtensionRegex = /(\.[^.]+)$/;
|
||||
}
|
||||
async onload() {
|
||||
console.log("loading " + this.manifest.name + " plugin");
|
||||
await this.loadSettings();
|
||||
this.addCommand({
|
||||
id: "find-unlinked-files",
|
||||
name: "Find orphaned files",
|
||||
callback: () => this.findOrphanedFiles()
|
||||
});
|
||||
this.addCommand({
|
||||
id: "find-unresolved-link",
|
||||
name: "Find broken links",
|
||||
callback: () => this.findBrokenLinks()
|
||||
});
|
||||
this.addCommand({
|
||||
id: "delete-unlinked-files",
|
||||
name: "Delete orphaned files with certain extension. See README",
|
||||
callback: () => this.deleteOrphanedFiles()
|
||||
});
|
||||
this.addCommand({
|
||||
id: "create-files-of-broken-links",
|
||||
name: "Create files of broken links",
|
||||
callback: () => this.createFilesOfBrokenLinks()
|
||||
});
|
||||
this.addCommand({
|
||||
id: "find-files-without-tags",
|
||||
name: "Find files without tags",
|
||||
callback: () => this.findFilesWithoutTags()
|
||||
});
|
||||
this.addCommand({
|
||||
id: "find-empty-files",
|
||||
name: "Find empty files",
|
||||
callback: () => this.findEmptyFiles()
|
||||
});
|
||||
this.addCommand({
|
||||
id: "delete-empty-files",
|
||||
name: "Delete empty files",
|
||||
callback: () => this.deleteEmptyFiles()
|
||||
});
|
||||
this.addSettingTab(new SettingsTab(this.app, this, DEFAULT_SETTINGS));
|
||||
this.app.workspace.on("file-menu", (menu, file, source, leaf) => {
|
||||
if (file instanceof import_obsidian4.TFolder) {
|
||||
menu.addItem((cb) => {
|
||||
cb.setIcon("search");
|
||||
cb.setTitle("Find orphaned files");
|
||||
cb.onClick((e) => {
|
||||
this.findOrphanedFiles(file.path + "/");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
async createFilesOfBrokenLinks() {
|
||||
var _a, _b;
|
||||
if (!await this.app.vault.adapter.exists(
|
||||
this.settings.unresolvedLinksOutputFileName + ".md"
|
||||
)) {
|
||||
new import_obsidian4.Notice(
|
||||
"Can't find file - Please run the `Find broken files' command before"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const links = (_a = this.app.metadataCache.getCache(
|
||||
this.settings.unresolvedLinksOutputFileName + ".md"
|
||||
)) == null ? void 0 : _a.links;
|
||||
if (!links) {
|
||||
new import_obsidian4.Notice("No broken links found");
|
||||
return;
|
||||
}
|
||||
const filesToCreate = [];
|
||||
for (const link of links) {
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(
|
||||
link.link,
|
||||
"/"
|
||||
);
|
||||
if (file)
|
||||
continue;
|
||||
const foundType = (_b = this.findExtensionRegex.exec(link.link)) == null ? void 0 : _b[0];
|
||||
if ((foundType != null ? foundType : ".md") == ".md") {
|
||||
if (foundType) {
|
||||
filesToCreate.push(link.link);
|
||||
} else {
|
||||
filesToCreate.push(link.link + ".md");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filesToCreate) {
|
||||
for (const file of filesToCreate) {
|
||||
await this.app.vault.create(file, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
async findEmptyFiles() {
|
||||
var _a;
|
||||
const files = this.app.vault.getFiles();
|
||||
const emptyFiles = [];
|
||||
for (const file of files) {
|
||||
if (!new Utils(
|
||||
this.app,
|
||||
file.path,
|
||||
[],
|
||||
[],
|
||||
this.settings.emptyFilesDirectories,
|
||||
this.settings.emptyFilesFilesToIgnore,
|
||||
this.settings.emptyFilesIgnoreDirectories
|
||||
).isValid()) {
|
||||
continue;
|
||||
}
|
||||
const content = await this.app.vault.read(file);
|
||||
const trimmedContent = content.trim();
|
||||
if (!trimmedContent) {
|
||||
emptyFiles.push(file);
|
||||
}
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
const frontmatter = cache == null ? void 0 : cache.frontmatter;
|
||||
if (frontmatter) {
|
||||
const lines = content.trimRight().split("\n").length;
|
||||
if (((_a = cache.frontmatterPosition) != null ? _a : frontmatter.position).end.line == lines - 1) {
|
||||
emptyFiles.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
let prefix;
|
||||
if (this.settings.disableWorkingLinks)
|
||||
prefix = " ";
|
||||
else
|
||||
prefix = "";
|
||||
const text = emptyFiles.map((file) => `${prefix}- [[${file.path}]]`).join("\n");
|
||||
Utils.writeAndOpenFile(
|
||||
this.app,
|
||||
this.settings.emptyFilesOutputFileName + ".md",
|
||||
text,
|
||||
this.settings.openOutputFile
|
||||
);
|
||||
}
|
||||
findOrphanedFiles(dir) {
|
||||
const outFileName = this.settings.outputFileName + ".md";
|
||||
let outFile;
|
||||
const files = this.app.vault.getFiles();
|
||||
const markdownFiles = this.app.vault.getMarkdownFiles();
|
||||
const links = [];
|
||||
markdownFiles.forEach((markFile) => {
|
||||
if (markFile.path == outFileName) {
|
||||
outFile = markFile;
|
||||
return;
|
||||
}
|
||||
(0, import_obsidian4.iterateCacheRefs)(
|
||||
this.app.metadataCache.getFileCache(markFile),
|
||||
(cb) => {
|
||||
const txt = this.app.metadataCache.getFirstLinkpathDest(
|
||||
(0, import_obsidian4.getLinkpath)(cb.link),
|
||||
markFile.path
|
||||
);
|
||||
if (txt != null)
|
||||
links.push(txt.path);
|
||||
}
|
||||
);
|
||||
});
|
||||
const notLinkedFiles = files.filter(
|
||||
(file) => this.isValid(file, links, dir)
|
||||
);
|
||||
notLinkedFiles.remove(outFile);
|
||||
let text = "";
|
||||
let prefix;
|
||||
if (this.settings.disableWorkingLinks)
|
||||
prefix = " ";
|
||||
else
|
||||
prefix = "";
|
||||
notLinkedFiles.sort((a, b) => b.stat.size - a.stat.size);
|
||||
notLinkedFiles.forEach((file) => {
|
||||
text += prefix + "- [[" + this.app.metadataCache.fileToLinktext(file, "/", false) + "]]\n";
|
||||
});
|
||||
Utils.writeAndOpenFile(
|
||||
this.app,
|
||||
outFileName,
|
||||
text,
|
||||
this.settings.openOutputFile
|
||||
);
|
||||
}
|
||||
async deleteOrphanedFiles() {
|
||||
var _a, _b;
|
||||
if (!await this.app.vault.adapter.exists(
|
||||
this.settings.outputFileName + ".md"
|
||||
)) {
|
||||
new import_obsidian4.Notice(
|
||||
"Can't find file - Please run the `Find orphaned files' command before"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const links = (_b = (_a = this.app.metadataCache.getCache(
|
||||
this.settings.outputFileName + ".md"
|
||||
)) == null ? void 0 : _a.links) != null ? _b : [];
|
||||
const filesToDelete = [];
|
||||
links.forEach((link) => {
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(
|
||||
link.link,
|
||||
"/"
|
||||
);
|
||||
if (!file)
|
||||
return;
|
||||
if (this.settings.fileTypesToDelete[0] == "*" || this.settings.fileTypesToDelete.contains(file.extension)) {
|
||||
filesToDelete.push(file);
|
||||
}
|
||||
});
|
||||
if (filesToDelete.length > 0)
|
||||
new DeleteFilesModal(this.app, filesToDelete).open();
|
||||
}
|
||||
async deleteEmptyFiles() {
|
||||
var _a, _b;
|
||||
if (!await this.app.vault.adapter.exists(
|
||||
this.settings.emptyFilesOutputFileName + ".md"
|
||||
)) {
|
||||
new import_obsidian4.Notice(
|
||||
"Can't find file - Please run the `Find orphaned files' command before"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const links = (_b = (_a = this.app.metadataCache.getCache(
|
||||
this.settings.emptyFilesOutputFileName + ".md"
|
||||
)) == null ? void 0 : _a.links) != null ? _b : [];
|
||||
const filesToDelete = [];
|
||||
for (const link of links) {
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(
|
||||
link.link,
|
||||
"/"
|
||||
);
|
||||
if (!file)
|
||||
return;
|
||||
filesToDelete.push(file);
|
||||
}
|
||||
if (filesToDelete.length > 0)
|
||||
new DeleteFilesModal(this.app, filesToDelete).open();
|
||||
}
|
||||
findBrokenLinks() {
|
||||
const outFileName = this.settings.unresolvedLinksOutputFileName + ".md";
|
||||
const links = [];
|
||||
const brokenLinks = this.app.metadataCache.unresolvedLinks;
|
||||
for (const sourceFilepath in brokenLinks) {
|
||||
if (sourceFilepath == this.settings.unresolvedLinksOutputFileName + ".md")
|
||||
continue;
|
||||
const fileType = sourceFilepath.substring(
|
||||
sourceFilepath.lastIndexOf(".") + 1
|
||||
);
|
||||
const utils = new Utils(
|
||||
this.app,
|
||||
sourceFilepath,
|
||||
this.settings.unresolvedLinksTagsToIgnore,
|
||||
this.settings.unresolvedLinksLinksToIgnore,
|
||||
this.settings.unresolvedLinksDirectoriesToIgnore,
|
||||
this.settings.unresolvedLinksFilesToIgnore,
|
||||
this.settings.unresolvedLinksIgnoreDirectories
|
||||
);
|
||||
if (!utils.isValid())
|
||||
continue;
|
||||
for (const link in brokenLinks[sourceFilepath]) {
|
||||
const linkFileType = link.substring(link.lastIndexOf(".") + 1);
|
||||
if (this.settings.unresolvedLinksFileTypesToIgnore.contains(
|
||||
linkFileType
|
||||
))
|
||||
continue;
|
||||
let formattedFilePath = sourceFilepath;
|
||||
if (fileType == "md") {
|
||||
formattedFilePath = sourceFilepath.substring(
|
||||
0,
|
||||
sourceFilepath.lastIndexOf(".md")
|
||||
);
|
||||
}
|
||||
const brokenLink = {
|
||||
files: [formattedFilePath],
|
||||
link
|
||||
};
|
||||
if (links.contains(brokenLink))
|
||||
continue;
|
||||
const duplication = links.find((e) => e.link == link);
|
||||
if (duplication) {
|
||||
duplication.files.push(formattedFilePath);
|
||||
} else {
|
||||
links.push(brokenLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
Utils.writeAndOpenFile(
|
||||
this.app,
|
||||
outFileName,
|
||||
[
|
||||
"Don't forget that creating the file from here may create the file in the wrong directory!",
|
||||
...links.map(
|
||||
(e) => `- [[${e.link}]] in [[${e.files.join("]], [[")}]]`
|
||||
)
|
||||
].join("\n"),
|
||||
this.settings.openOutputFile
|
||||
);
|
||||
}
|
||||
findFilesWithoutTags() {
|
||||
const outFileName = this.settings.withoutTagsOutputFileName + ".md";
|
||||
let outFile;
|
||||
const files = this.app.vault.getMarkdownFiles();
|
||||
let withoutFiles = files.filter((file) => {
|
||||
var _a;
|
||||
if (new Utils(
|
||||
this.app,
|
||||
file.path,
|
||||
[],
|
||||
[],
|
||||
this.settings.withoutTagsDirectoriesToIgnore,
|
||||
this.settings.withoutTagsFilesToIgnore,
|
||||
true
|
||||
).isValid()) {
|
||||
return ((_a = (0, import_obsidian4.getAllTags)(this.app.metadataCache.getFileCache(file)).length) != null ? _a : 0) <= 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
withoutFiles.remove(outFile);
|
||||
let prefix;
|
||||
if (this.settings.disableWorkingLinks)
|
||||
prefix = " ";
|
||||
else
|
||||
prefix = "";
|
||||
const text = withoutFiles.map((file) => `${prefix}- [[${file.path}]]`).join("\n");
|
||||
Utils.writeAndOpenFile(
|
||||
this.app,
|
||||
outFileName,
|
||||
text,
|
||||
this.settings.openOutputFile
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Checks if the given file in an orphaned file
|
||||
*
|
||||
* @param file file to check
|
||||
* @param links all links in the vault
|
||||
*/
|
||||
isValid(file, links, dir) {
|
||||
if (links.contains(file.path))
|
||||
return false;
|
||||
if (file.extension == "css")
|
||||
return false;
|
||||
if (this.settings.fileTypesToIgnore[0] !== "") {
|
||||
const containsFileType = this.settings.fileTypesToIgnore.contains(
|
||||
file.extension
|
||||
);
|
||||
if (this.settings.ignoreFileTypes) {
|
||||
if (containsFileType)
|
||||
return;
|
||||
} else {
|
||||
if (!containsFileType)
|
||||
return;
|
||||
}
|
||||
}
|
||||
const utils = new Utils(
|
||||
this.app,
|
||||
file.path,
|
||||
this.settings.tagsToIgnore,
|
||||
this.settings.linksToIgnore,
|
||||
this.settings.directoriesToIgnore,
|
||||
this.settings.filesToIgnore,
|
||||
this.settings.ignoreDirectories,
|
||||
dir
|
||||
);
|
||||
if (!utils.isValid())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
onunload() {
|
||||
console.log("unloading " + this.manifest.name + " plugin");
|
||||
}
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "find-unlinked-files",
|
||||
"name": "Find orphaned files and broken links",
|
||||
"version": "1.9.1",
|
||||
"description": "Find files that are not linked anywhere and would otherwise be lost in your vault. In other words: files with no backlinks.",
|
||||
"author": "Vinzent",
|
||||
"fundingUrl": "https://ko-fi.com/vinzent",
|
||||
"authorUrl": "https://github.com/Vinzent03",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
@@ -1,942 +0,0 @@
|
||||
/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD */
|
||||
var __defProp = Object.defineProperty;
|
||||
var __defProps = Object.defineProperties;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __spreadValues = (a, b) => {
|
||||
for (var prop in b || (b = {}))
|
||||
if (__hasOwnProp.call(b, prop))
|
||||
__defNormalProp(a, prop, b[prop]);
|
||||
if (__getOwnPropSymbols)
|
||||
for (var prop of __getOwnPropSymbols(b)) {
|
||||
if (__propIsEnum.call(b, prop))
|
||||
__defNormalProp(a, prop, b[prop]);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
||||
var __commonJS = (cb, mod) => function __require() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
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);
|
||||
var __async = (__this, __arguments, generator) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var fulfilled = (value) => {
|
||||
try {
|
||||
step(generator.next(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var rejected = (value) => {
|
||||
try {
|
||||
step(generator.throw(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
||||
step((generator = generator.apply(__this, __arguments)).next());
|
||||
});
|
||||
};
|
||||
|
||||
// package.json
|
||||
var require_package = __commonJS({
|
||||
"package.json"(exports, module2) {
|
||||
module2.exports = {
|
||||
name: "obsidian-paste-image-rename",
|
||||
version: "1.6.1",
|
||||
main: "main.js",
|
||||
scripts: {
|
||||
start: "node esbuild.config.mjs",
|
||||
build: "tsc -noEmit -skipLibCheck && BUILD_ENV=production node esbuild.config.mjs && cp manifest.json build",
|
||||
version: "node version-bump.mjs && git add manifest.json versions.json",
|
||||
release: "npm run build && gh release create ${npm_package_version} build/*"
|
||||
},
|
||||
keywords: [],
|
||||
author: "Reorx",
|
||||
license: "MIT",
|
||||
devDependencies: {
|
||||
"@types/node": "^18.11.18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/parser": "^5.49.0",
|
||||
"builtin-modules": "^3.3.0",
|
||||
esbuild: "0.16.17",
|
||||
obsidian: "^1.1.1",
|
||||
tslib: "2.5.0",
|
||||
typescript: "4.9.4"
|
||||
},
|
||||
dependencies: {
|
||||
"cash-dom": "^8.1.2"
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// src/main.ts
|
||||
var main_exports = {};
|
||||
__export(main_exports, {
|
||||
default: () => PasteImageRenamePlugin
|
||||
});
|
||||
module.exports = __toCommonJS(main_exports);
|
||||
var import_obsidian2 = require("obsidian");
|
||||
|
||||
// src/batch.ts
|
||||
var import_obsidian = require("obsidian");
|
||||
|
||||
// src/utils.ts
|
||||
var DEBUG = false;
|
||||
if (DEBUG)
|
||||
console.log("DEBUG is enabled");
|
||||
function debugLog(...args) {
|
||||
if (DEBUG) {
|
||||
console.log(new Date().toISOString().slice(11, 23), ...args);
|
||||
}
|
||||
}
|
||||
function createElementTree(rootEl, opts) {
|
||||
const result = {
|
||||
el: rootEl.createEl(opts.tag, opts),
|
||||
children: []
|
||||
};
|
||||
const children = opts.children || [];
|
||||
for (const child of children) {
|
||||
result.children.push(createElementTree(result.el, child));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
var path = {
|
||||
// Credit: @creationix/path.js
|
||||
join(...partSegments) {
|
||||
let parts = [];
|
||||
for (let i = 0, l = partSegments.length; i < l; i++) {
|
||||
parts = parts.concat(partSegments[i].split("/"));
|
||||
}
|
||||
const newParts = [];
|
||||
for (let i = 0, l = parts.length; i < l; i++) {
|
||||
const part = parts[i];
|
||||
if (!part || part === ".")
|
||||
continue;
|
||||
else
|
||||
newParts.push(part);
|
||||
}
|
||||
if (parts[0] === "")
|
||||
newParts.unshift("");
|
||||
return newParts.join("/");
|
||||
},
|
||||
// returns the last part of a path, e.g. 'foo.jpg'
|
||||
basename(fullpath) {
|
||||
const sp = fullpath.split("/");
|
||||
return sp[sp.length - 1];
|
||||
},
|
||||
// return extension without dot, e.g. 'jpg'
|
||||
extension(fullpath) {
|
||||
const positions = [...fullpath.matchAll(new RegExp("\\.", "gi"))].map((a) => a.index);
|
||||
return fullpath.slice(positions[positions.length - 1] + 1);
|
||||
}
|
||||
};
|
||||
var filenameNotAllowedChars = /[^\p{L}0-9~`!@$&*()\-_=+{};'",<.>? ]/ug;
|
||||
var sanitizer = {
|
||||
filename(s) {
|
||||
return s.replace(filenameNotAllowedChars, "").trim();
|
||||
},
|
||||
delimiter(s) {
|
||||
s = this.filename(s);
|
||||
if (!s)
|
||||
s = "-";
|
||||
return s;
|
||||
}
|
||||
};
|
||||
function escapeRegExp(s) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
function lockInputMethodComposition(el) {
|
||||
const state = {
|
||||
lock: false
|
||||
};
|
||||
el.addEventListener("compositionstart", () => {
|
||||
state.lock = true;
|
||||
});
|
||||
el.addEventListener("compositionend", () => {
|
||||
state.lock = false;
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
// src/batch.ts
|
||||
var ImageBatchRenameModal = class extends import_obsidian.Modal {
|
||||
constructor(app, activeFile, renameFunc, onClose) {
|
||||
super(app);
|
||||
this.activeFile = activeFile;
|
||||
this.renameFunc = renameFunc;
|
||||
this.onCloseExtra = onClose;
|
||||
this.state = {
|
||||
namePattern: "",
|
||||
extPattern: "",
|
||||
nameReplace: "",
|
||||
renameTasks: []
|
||||
};
|
||||
}
|
||||
onOpen() {
|
||||
this.containerEl.addClass("image-rename-modal");
|
||||
const { contentEl, titleEl } = this;
|
||||
titleEl.setText("Batch rename embeded files");
|
||||
const namePatternSetting = new import_obsidian.Setting(contentEl).setName("Name pattern").setDesc("Please input the name pattern to match files (regex)").addText((text) => text.setValue(this.state.namePattern).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.state.namePattern = value;
|
||||
})
|
||||
));
|
||||
const npInputEl = namePatternSetting.controlEl.children[0];
|
||||
npInputEl.focus();
|
||||
const npInputState = lockInputMethodComposition(npInputEl);
|
||||
npInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
|
||||
if (e.key === "Enter" && !npInputState.lock) {
|
||||
e.preventDefault();
|
||||
if (!this.state.namePattern) {
|
||||
errorEl.innerText = 'Error: "Name pattern" could not be empty';
|
||||
errorEl.style.display = "block";
|
||||
return;
|
||||
}
|
||||
this.matchImageNames(tbodyEl);
|
||||
}
|
||||
}));
|
||||
const extPatternSetting = new import_obsidian.Setting(contentEl).setName("Extension pattern").setDesc("Please input the extension pattern to match files (regex)").addText((text) => text.setValue(this.state.extPattern).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.state.extPattern = value;
|
||||
})
|
||||
));
|
||||
const extInputEl = extPatternSetting.controlEl.children[0];
|
||||
extInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
this.matchImageNames(tbodyEl);
|
||||
}
|
||||
}));
|
||||
const nameReplaceSetting = new import_obsidian.Setting(contentEl).setName("Name replace").setDesc("Please input the string to replace the matched name (use $1, $2 for regex groups)").addText((text) => text.setValue(this.state.nameReplace).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.state.nameReplace = value;
|
||||
})
|
||||
));
|
||||
const nrInputEl = nameReplaceSetting.controlEl.children[0];
|
||||
const nrInputState = lockInputMethodComposition(nrInputEl);
|
||||
nrInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
|
||||
if (e.key === "Enter" && !nrInputState.lock) {
|
||||
e.preventDefault();
|
||||
this.matchImageNames(tbodyEl);
|
||||
}
|
||||
}));
|
||||
const matchedContainer = contentEl.createDiv({
|
||||
cls: "matched-container"
|
||||
});
|
||||
const tableET = createElementTree(matchedContainer, {
|
||||
tag: "table",
|
||||
children: [
|
||||
{
|
||||
tag: "thead",
|
||||
children: [
|
||||
{
|
||||
tag: "tr",
|
||||
children: [
|
||||
{
|
||||
tag: "td",
|
||||
text: "Original path"
|
||||
},
|
||||
{
|
||||
tag: "td",
|
||||
text: "Renamed Name"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
tag: "tbody"
|
||||
}
|
||||
]
|
||||
});
|
||||
const tbodyEl = tableET.children[1].el;
|
||||
const errorEl = contentEl.createDiv({
|
||||
cls: "error",
|
||||
attr: {
|
||||
style: "display: none;"
|
||||
}
|
||||
});
|
||||
new import_obsidian.Setting(contentEl).addButton((button) => {
|
||||
button.setButtonText("Rename all").setClass("mod-cta").onClick(() => {
|
||||
new ConfirmModal(
|
||||
this.app,
|
||||
"Confirm rename all",
|
||||
`Are you sure? This will rename all the ${this.state.renameTasks.length} images matched the pattern.`,
|
||||
() => {
|
||||
this.renameAll();
|
||||
this.close();
|
||||
}
|
||||
).open();
|
||||
});
|
||||
}).addButton((button) => {
|
||||
button.setButtonText("Cancel").onClick(() => {
|
||||
this.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
this.onCloseExtra();
|
||||
}
|
||||
renameAll() {
|
||||
return __async(this, null, function* () {
|
||||
debugLog("renameAll", this.state);
|
||||
for (const task of this.state.renameTasks) {
|
||||
yield this.renameFunc(task.file, task.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
matchImageNames(tbodyEl) {
|
||||
const { state } = this;
|
||||
const renameTasks = [];
|
||||
tbodyEl.empty();
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.activeFile);
|
||||
if (!fileCache || !fileCache.embeds)
|
||||
return;
|
||||
const namePatternRegex = new RegExp(state.namePattern, "g");
|
||||
const extPatternRegex = new RegExp(state.extPattern);
|
||||
fileCache.embeds.forEach((embed) => {
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, this.activeFile.path);
|
||||
if (!file) {
|
||||
console.warn("file not found", embed.link);
|
||||
return;
|
||||
}
|
||||
if (state.extPattern) {
|
||||
const m0 = extPatternRegex.exec(file.extension);
|
||||
if (!m0)
|
||||
return;
|
||||
}
|
||||
const stem = file.basename;
|
||||
namePatternRegex.lastIndex = 0;
|
||||
const m1 = namePatternRegex.exec(stem);
|
||||
if (!m1)
|
||||
return;
|
||||
let renamedName = file.name;
|
||||
if (state.nameReplace) {
|
||||
namePatternRegex.lastIndex = 0;
|
||||
renamedName = stem.replace(namePatternRegex, state.nameReplace);
|
||||
renamedName = `${renamedName}.${file.extension}`;
|
||||
}
|
||||
renameTasks.push({
|
||||
file,
|
||||
name: renamedName
|
||||
});
|
||||
createElementTree(tbodyEl, {
|
||||
tag: "tr",
|
||||
children: [
|
||||
{
|
||||
tag: "td",
|
||||
children: [
|
||||
{
|
||||
tag: "span",
|
||||
text: file.name
|
||||
},
|
||||
{
|
||||
tag: "div",
|
||||
text: file.path,
|
||||
attr: {
|
||||
class: "file-path"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
tag: "td",
|
||||
children: [
|
||||
{
|
||||
tag: "span",
|
||||
text: renamedName
|
||||
},
|
||||
{
|
||||
tag: "div",
|
||||
text: path.join(file.parent.path, renamedName),
|
||||
attr: {
|
||||
class: "file-path"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
debugLog("new renameTasks", renameTasks);
|
||||
state.renameTasks = renameTasks;
|
||||
}
|
||||
};
|
||||
var ConfirmModal = class extends import_obsidian.Modal {
|
||||
constructor(app, title, message, onConfirm) {
|
||||
super(app);
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.onConfirm = onConfirm;
|
||||
}
|
||||
onOpen() {
|
||||
const { contentEl, titleEl } = this;
|
||||
titleEl.setText(this.title);
|
||||
contentEl.createEl("p", {
|
||||
text: this.message
|
||||
});
|
||||
new import_obsidian.Setting(contentEl).addButton((button) => {
|
||||
button.setButtonText("Yes").setClass("mod-warning").onClick(() => {
|
||||
this.onConfirm();
|
||||
this.close();
|
||||
});
|
||||
}).addButton((button) => {
|
||||
button.setButtonText("No").onClick(() => {
|
||||
this.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// src/template.ts
|
||||
var dateTmplRegex = /{{DATE:([^}]+)}}/gm;
|
||||
var frontmatterTmplRegex = /{{frontmatter:([^}]+)}}/gm;
|
||||
var replaceDateVar = (s, date) => {
|
||||
const m = dateTmplRegex.exec(s);
|
||||
if (!m)
|
||||
return s;
|
||||
return s.replace(m[0], date.format(m[1]));
|
||||
};
|
||||
var replaceFrontmatterVar = (s, frontmatter) => {
|
||||
if (!frontmatter)
|
||||
return s;
|
||||
const m = frontmatterTmplRegex.exec(s);
|
||||
if (!m)
|
||||
return s;
|
||||
return s.replace(m[0], frontmatter[m[1]] || "");
|
||||
};
|
||||
var renderTemplate = (tmpl, data, frontmatter) => {
|
||||
const now = window.moment();
|
||||
let text = tmpl;
|
||||
let newtext;
|
||||
while ((newtext = replaceDateVar(text, now)) != text) {
|
||||
text = newtext;
|
||||
}
|
||||
while ((newtext = replaceFrontmatterVar(text, frontmatter)) != text) {
|
||||
text = newtext;
|
||||
}
|
||||
text = text.replace(/{{imageNameKey}}/gm, data.imageNameKey).replace(/{{fileName}}/gm, data.fileName).replace(/{{dirName}}/gm, data.dirName).replace(/{{firstHeading}}/gm, data.firstHeading);
|
||||
return text;
|
||||
};
|
||||
|
||||
// src/main.ts
|
||||
var DEFAULT_SETTINGS = {
|
||||
imageNamePattern: "{{fileName}}",
|
||||
dupNumberAtStart: false,
|
||||
dupNumberDelimiter: "-",
|
||||
dupNumberAlways: false,
|
||||
autoRename: false,
|
||||
handleAllAttachments: false,
|
||||
excludeExtensionPattern: "",
|
||||
disableRenameNotice: false
|
||||
};
|
||||
var PASTED_IMAGE_PREFIX = "Pasted image ";
|
||||
var PasteImageRenamePlugin = class extends import_obsidian2.Plugin {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.modals = [];
|
||||
}
|
||||
onload() {
|
||||
return __async(this, null, function* () {
|
||||
const pkg = require_package();
|
||||
console.log(`Plugin loading: ${pkg.name} ${pkg.version} BUILD_ENV=${"production"}`);
|
||||
yield this.loadSettings();
|
||||
this.registerEvent(
|
||||
this.app.vault.on("create", (file) => {
|
||||
if (!(file instanceof import_obsidian2.TFile))
|
||||
return;
|
||||
const timeGapMs = new Date().getTime() - file.stat.ctime;
|
||||
if (timeGapMs > 1e3)
|
||||
return;
|
||||
if (isMarkdownFile(file))
|
||||
return;
|
||||
if (isPastedImage(file)) {
|
||||
debugLog("pasted image created", file);
|
||||
this.startRenameProcess(file, this.settings.autoRename);
|
||||
} else {
|
||||
if (this.settings.handleAllAttachments) {
|
||||
debugLog("handleAllAttachments for file", file);
|
||||
if (this.testExcludeExtension(file)) {
|
||||
debugLog("excluded file by ext", file);
|
||||
return;
|
||||
}
|
||||
this.startRenameProcess(file, this.settings.autoRename);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
const startBatchRenameProcess = () => {
|
||||
this.openBatchRenameModal();
|
||||
};
|
||||
this.addCommand({
|
||||
id: "batch-rename-embeded-files",
|
||||
name: "Batch rename embeded files (in the current file)",
|
||||
callback: startBatchRenameProcess
|
||||
});
|
||||
if (DEBUG) {
|
||||
this.addRibbonIcon("wand-glyph", "Batch rename embeded files", startBatchRenameProcess);
|
||||
}
|
||||
const batchRenameAllImages = () => {
|
||||
this.batchRenameAllImages();
|
||||
};
|
||||
this.addCommand({
|
||||
id: "batch-rename-all-images",
|
||||
name: "Batch rename all images instantly (in the current file)",
|
||||
callback: batchRenameAllImages
|
||||
});
|
||||
if (DEBUG) {
|
||||
this.addRibbonIcon("wand-glyph", "Batch rename all images instantly (in the current file)", batchRenameAllImages);
|
||||
}
|
||||
this.addSettingTab(new SettingTab(this.app, this));
|
||||
});
|
||||
}
|
||||
startRenameProcess(file, autoRename = false) {
|
||||
return __async(this, null, function* () {
|
||||
const activeFile = this.getActiveFile();
|
||||
if (!activeFile) {
|
||||
new import_obsidian2.Notice("Error: No active file found.");
|
||||
return;
|
||||
}
|
||||
const { stem, newName, isMeaningful } = this.generateNewName(file, activeFile);
|
||||
debugLog("generated newName:", newName, isMeaningful);
|
||||
if (!isMeaningful || !autoRename) {
|
||||
this.openRenameModal(file, isMeaningful ? stem : "", activeFile.path);
|
||||
return;
|
||||
}
|
||||
this.renameFile(file, newName, activeFile.path, true);
|
||||
});
|
||||
}
|
||||
renameFile(file, inputNewName, sourcePath, replaceCurrentLine) {
|
||||
return __async(this, null, function* () {
|
||||
const { name: newName } = yield this.deduplicateNewName(inputNewName, file);
|
||||
debugLog("deduplicated newName:", newName);
|
||||
const originName = file.name;
|
||||
const linkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
|
||||
const newPath = path.join(file.parent.path, newName);
|
||||
try {
|
||||
yield this.app.fileManager.renameFile(file, newPath);
|
||||
} catch (err) {
|
||||
new import_obsidian2.Notice(`Failed to rename ${newName}: ${err}`);
|
||||
throw err;
|
||||
}
|
||||
if (!replaceCurrentLine) {
|
||||
return;
|
||||
}
|
||||
const newLinkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
|
||||
debugLog("replace text", linkText, newLinkText);
|
||||
const editor = this.getActiveEditor();
|
||||
if (!editor) {
|
||||
new import_obsidian2.Notice(`Failed to rename ${newName}: no active editor`);
|
||||
return;
|
||||
}
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const replacedLine = line.replace(linkText, newLinkText);
|
||||
debugLog("current line -> replaced line", line, replacedLine);
|
||||
editor.transaction({
|
||||
changes: [
|
||||
{
|
||||
from: __spreadProps(__spreadValues({}, cursor), { ch: 0 }),
|
||||
to: __spreadProps(__spreadValues({}, cursor), { ch: line.length }),
|
||||
text: replacedLine
|
||||
}
|
||||
]
|
||||
});
|
||||
if (!this.settings.disableRenameNotice) {
|
||||
new import_obsidian2.Notice(`Renamed ${originName} to ${newName}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
openRenameModal(file, newName, sourcePath) {
|
||||
const modal = new ImageRenameModal(
|
||||
this.app,
|
||||
file,
|
||||
newName,
|
||||
(confirmedName) => {
|
||||
debugLog("confirmedName:", confirmedName);
|
||||
this.renameFile(file, confirmedName, sourcePath, true);
|
||||
},
|
||||
() => {
|
||||
this.modals.splice(this.modals.indexOf(modal), 1);
|
||||
}
|
||||
);
|
||||
this.modals.push(modal);
|
||||
modal.open();
|
||||
debugLog("modals count", this.modals.length);
|
||||
}
|
||||
openBatchRenameModal() {
|
||||
const activeFile = this.getActiveFile();
|
||||
const modal = new ImageBatchRenameModal(
|
||||
this.app,
|
||||
activeFile,
|
||||
(file, name) => __async(this, null, function* () {
|
||||
yield this.renameFile(file, name, activeFile.path);
|
||||
}),
|
||||
() => {
|
||||
this.modals.splice(this.modals.indexOf(modal), 1);
|
||||
}
|
||||
);
|
||||
this.modals.push(modal);
|
||||
modal.open();
|
||||
}
|
||||
batchRenameAllImages() {
|
||||
return __async(this, null, function* () {
|
||||
const activeFile = this.getActiveFile();
|
||||
const fileCache = this.app.metadataCache.getFileCache(activeFile);
|
||||
if (!fileCache || !fileCache.embeds)
|
||||
return;
|
||||
const extPatternRegex = /jpe?g|png|gif|tiff|webp/i;
|
||||
for (const embed of fileCache.embeds) {
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, activeFile.path);
|
||||
if (!file) {
|
||||
console.warn("file not found", embed.link);
|
||||
return;
|
||||
}
|
||||
const m0 = extPatternRegex.exec(file.extension);
|
||||
if (!m0)
|
||||
return;
|
||||
const { newName, isMeaningful } = this.generateNewName(file, activeFile);
|
||||
debugLog("generated newName:", newName, isMeaningful);
|
||||
if (!isMeaningful) {
|
||||
new import_obsidian2.Notice("Failed to batch rename images: the generated name is not meaningful");
|
||||
break;
|
||||
}
|
||||
yield this.renameFile(file, newName, activeFile.path, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
// returns a new name for the input file, with extension
|
||||
generateNewName(file, activeFile) {
|
||||
let imageNameKey = "";
|
||||
let firstHeading = "";
|
||||
let frontmatter;
|
||||
const fileCache = this.app.metadataCache.getFileCache(activeFile);
|
||||
if (fileCache) {
|
||||
debugLog("frontmatter", fileCache.frontmatter);
|
||||
frontmatter = fileCache.frontmatter;
|
||||
imageNameKey = (frontmatter == null ? void 0 : frontmatter.imageNameKey) || "";
|
||||
firstHeading = getFirstHeading(fileCache.headings);
|
||||
} else {
|
||||
console.warn("could not get file cache from active file", activeFile.name);
|
||||
}
|
||||
const stem = renderTemplate(
|
||||
this.settings.imageNamePattern,
|
||||
{
|
||||
imageNameKey,
|
||||
fileName: activeFile.basename,
|
||||
dirName: activeFile.parent.name,
|
||||
firstHeading
|
||||
},
|
||||
frontmatter
|
||||
);
|
||||
const meaninglessRegex = new RegExp(`[${this.settings.dupNumberDelimiter}\\s]`, "gm");
|
||||
return {
|
||||
stem,
|
||||
newName: stem + "." + file.extension,
|
||||
isMeaningful: stem.replace(meaninglessRegex, "") !== ""
|
||||
};
|
||||
}
|
||||
// newName: foo.ext
|
||||
deduplicateNewName(newName, file) {
|
||||
return __async(this, null, function* () {
|
||||
const dir = file.parent.path;
|
||||
const listed = yield this.app.vault.adapter.list(dir);
|
||||
debugLog("sibling files", listed);
|
||||
const newNameExt = path.extension(newName), newNameStem = newName.slice(0, newName.length - newNameExt.length - 1), newNameStemEscaped = escapeRegExp(newNameStem), delimiter = this.settings.dupNumberDelimiter, delimiterEscaped = escapeRegExp(delimiter);
|
||||
let dupNameRegex;
|
||||
if (this.settings.dupNumberAtStart) {
|
||||
dupNameRegex = new RegExp(
|
||||
`^(?<number>\\d+)${delimiterEscaped}(?<name>${newNameStemEscaped})\\.${newNameExt}$`
|
||||
);
|
||||
} else {
|
||||
dupNameRegex = new RegExp(
|
||||
`^(?<name>${newNameStemEscaped})${delimiterEscaped}(?<number>\\d+)\\.${newNameExt}$`
|
||||
);
|
||||
}
|
||||
debugLog("dupNameRegex", dupNameRegex);
|
||||
const dupNameNumbers = [];
|
||||
let isNewNameExist = false;
|
||||
for (let sibling of listed.files) {
|
||||
sibling = path.basename(sibling);
|
||||
if (sibling == newName) {
|
||||
isNewNameExist = true;
|
||||
continue;
|
||||
}
|
||||
const m = dupNameRegex.exec(sibling);
|
||||
if (!m)
|
||||
continue;
|
||||
dupNameNumbers.push(parseInt(m.groups.number));
|
||||
}
|
||||
if (isNewNameExist || this.settings.dupNumberAlways) {
|
||||
const newNumber = dupNameNumbers.length > 0 ? Math.max(...dupNameNumbers) + 1 : 1;
|
||||
if (this.settings.dupNumberAtStart) {
|
||||
newName = `${newNumber}${delimiter}${newNameStem}.${newNameExt}`;
|
||||
} else {
|
||||
newName = `${newNameStem}${delimiter}${newNumber}.${newNameExt}`;
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: newName,
|
||||
stem: newName.slice(0, newName.length - newNameExt.length - 1),
|
||||
extension: newNameExt
|
||||
};
|
||||
});
|
||||
}
|
||||
getActiveFile() {
|
||||
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
|
||||
const file = view == null ? void 0 : view.file;
|
||||
debugLog("active file", file == null ? void 0 : file.path);
|
||||
return file;
|
||||
}
|
||||
getActiveEditor() {
|
||||
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
|
||||
return view == null ? void 0 : view.editor;
|
||||
}
|
||||
onunload() {
|
||||
this.modals.map((modal) => modal.close());
|
||||
}
|
||||
testExcludeExtension(file) {
|
||||
const pattern = this.settings.excludeExtensionPattern;
|
||||
if (!pattern)
|
||||
return false;
|
||||
return new RegExp(pattern).test(file.extension);
|
||||
}
|
||||
loadSettings() {
|
||||
return __async(this, null, function* () {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
|
||||
});
|
||||
}
|
||||
saveSettings() {
|
||||
return __async(this, null, function* () {
|
||||
yield this.saveData(this.settings);
|
||||
});
|
||||
}
|
||||
};
|
||||
function getFirstHeading(headings) {
|
||||
if (headings && headings.length > 0) {
|
||||
for (const heading of headings) {
|
||||
if (heading.level === 1) {
|
||||
return heading.heading;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function isPastedImage(file) {
|
||||
if (file instanceof import_obsidian2.TFile) {
|
||||
if (file.name.startsWith(PASTED_IMAGE_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function isMarkdownFile(file) {
|
||||
if (file instanceof import_obsidian2.TFile) {
|
||||
if (file.extension === "md") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var ImageRenameModal = class extends import_obsidian2.Modal {
|
||||
constructor(app, src, stem, renameFunc, onClose) {
|
||||
super(app);
|
||||
this.src = src;
|
||||
this.stem = stem;
|
||||
this.renameFunc = renameFunc;
|
||||
this.onCloseExtra = onClose;
|
||||
}
|
||||
onOpen() {
|
||||
this.containerEl.addClass("image-rename-modal");
|
||||
const { contentEl, titleEl } = this;
|
||||
titleEl.setText("Rename image");
|
||||
const imageContainer = contentEl.createDiv({
|
||||
cls: "image-container"
|
||||
});
|
||||
imageContainer.createEl("img", {
|
||||
attr: {
|
||||
src: this.app.vault.getResourcePath(this.src)
|
||||
}
|
||||
});
|
||||
let stem = this.stem;
|
||||
const ext = this.src.extension;
|
||||
const getNewName = (stem2) => stem2 + "." + ext;
|
||||
const getNewPath = (stem2) => path.join(this.src.parent.path, getNewName(stem2));
|
||||
const infoET = createElementTree(contentEl, {
|
||||
tag: "ul",
|
||||
cls: "info",
|
||||
children: [
|
||||
{
|
||||
tag: "li",
|
||||
children: [
|
||||
{
|
||||
tag: "span",
|
||||
text: "Origin path"
|
||||
},
|
||||
{
|
||||
tag: "span",
|
||||
text: this.src.path
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
tag: "li",
|
||||
children: [
|
||||
{
|
||||
tag: "span",
|
||||
text: "New path"
|
||||
},
|
||||
{
|
||||
tag: "span",
|
||||
text: getNewPath(stem)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
const doRename = () => __async(this, null, function* () {
|
||||
debugLog("doRename", `stem=${stem}`);
|
||||
this.renameFunc(getNewName(stem));
|
||||
});
|
||||
const nameSetting = new import_obsidian2.Setting(contentEl).setName("New name").setDesc("Please input the new name for the image (without extension)").addText((text) => text.setValue(stem).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
stem = sanitizer.filename(value);
|
||||
infoET.children[1].children[1].el.innerText = getNewPath(stem);
|
||||
})
|
||||
));
|
||||
const nameInputEl = nameSetting.controlEl.children[0];
|
||||
nameInputEl.focus();
|
||||
const nameInputState = lockInputMethodComposition(nameInputEl);
|
||||
nameInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
|
||||
if (e.key === "Enter" && !nameInputState.lock) {
|
||||
e.preventDefault();
|
||||
if (!stem) {
|
||||
errorEl.innerText = 'Error: "New name" could not be empty';
|
||||
errorEl.style.display = "block";
|
||||
return;
|
||||
}
|
||||
doRename();
|
||||
this.close();
|
||||
}
|
||||
}));
|
||||
const errorEl = contentEl.createDiv({
|
||||
cls: "error",
|
||||
attr: {
|
||||
style: "display: none;"
|
||||
}
|
||||
});
|
||||
new import_obsidian2.Setting(contentEl).addButton((button) => {
|
||||
button.setButtonText("Rename").onClick(() => {
|
||||
doRename();
|
||||
this.close();
|
||||
});
|
||||
}).addButton((button) => {
|
||||
button.setButtonText("Cancel").onClick(() => {
|
||||
this.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
this.onCloseExtra();
|
||||
}
|
||||
};
|
||||
var imageNamePatternDesc = `
|
||||
The pattern indicates how the new name should be generated.
|
||||
|
||||
Available variables:
|
||||
- {{fileName}}: name of the active file, without ".md" extension.
|
||||
- {{imageNameKey}}: this variable is read from the markdown file's frontmatter, from the same key "imageNameKey".
|
||||
- {{DATE:$FORMAT}}: use "$FORMAT" to format the current date, "$FORMAT" must be a Moment.js format string, e.g. {{DATE:YYYY-MM-DD}}.
|
||||
|
||||
Here are some examples from pattern to image names (repeat in sequence), variables: fileName = "My note", imageNameKey = "foo":
|
||||
- {{fileName}}: My note, My note-1, My note-2
|
||||
- {{imageNameKey}}: foo, foo-1, foo-2
|
||||
- {{imageNameKey}}-{{DATE:YYYYMMDD}}: foo-20220408, foo-20220408-1, foo-20220408-2
|
||||
`;
|
||||
var SettingTab = class extends import_obsidian2.PluginSettingTab {
|
||||
constructor(app, plugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
display() {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
new import_obsidian2.Setting(containerEl).setName("Image name pattern").setDesc(imageNamePatternDesc).setClass("long-description-setting-item").addText((text) => text.setPlaceholder("{{imageNameKey}}").setValue(this.plugin.settings.imageNamePattern).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.imageNamePattern = value;
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
new import_obsidian2.Setting(containerEl).setName("Duplicate number at start (or end)").setDesc(`If enabled, duplicate number will be added at the start as prefix for the image name, otherwise it will be added at the end as suffix for the image name.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAtStart).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.dupNumberAtStart = value;
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
new import_obsidian2.Setting(containerEl).setName("Duplicate number delimiter").setDesc(`The delimiter to generate the number prefix/suffix for duplicated names. For example, if the value is "-", the suffix will be like "-1", "-2", "-3", and the prefix will be like "1-", "2-", "3-". Only characters that are valid in file names are allowed.`).addText((text) => text.setValue(this.plugin.settings.dupNumberDelimiter).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.dupNumberDelimiter = sanitizer.delimiter(value);
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
new import_obsidian2.Setting(containerEl).setName("Always add duplicate number").setDesc(`If enabled, duplicate number will always be added to the image name. Otherwise, it will only be added when the name is duplicated.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAlways).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.dupNumberAlways = value;
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
new import_obsidian2.Setting(containerEl).setName("Auto rename").setDesc(`By default, the rename modal will always be shown to confirm before renaming, if this option is set, the image will be auto renamed after pasting.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.autoRename).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.autoRename = value;
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
new import_obsidian2.Setting(containerEl).setName("Handle all attachments").setDesc(`By default, the plugin only handles images that starts with "Pasted image " in name,
|
||||
which is the prefix Obsidian uses to create images from pasted content.
|
||||
If this option is set, the plugin will handle all attachments that are created in the vault.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.handleAllAttachments).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.handleAllAttachments = value;
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
new import_obsidian2.Setting(containerEl).setName("Exclude extension pattern").setDesc(`This option is only useful when "Handle all attachments" is enabled.
|
||||
Write a Regex pattern to exclude certain extensions from being handled. Only the first line will be used.`).setClass("single-line-textarea").addTextArea((text) => text.setPlaceholder("docx?|xlsx?|pptx?|zip|rar").setValue(this.plugin.settings.excludeExtensionPattern).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.excludeExtensionPattern = value;
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
new import_obsidian2.Setting(containerEl).setName("Disable rename notice").setDesc(`Turn off this option if you don't want to see the notice when renaming images.
|
||||
Note that Obsidian may display a notice when a link has changed, this option cannot disable that.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.disableRenameNotice).onChange(
|
||||
(value) => __async(this, null, function* () {
|
||||
this.plugin.settings.disableRenameNotice = value;
|
||||
yield this.plugin.saveSettings();
|
||||
})
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "obsidian-paste-image-rename",
|
||||
"name": "Paste image rename",
|
||||
"version": "1.6.1",
|
||||
"minAppVersion": "0.12.0",
|
||||
"description": "Rename pasted images and all the other attchments added to the vault",
|
||||
"author": "Reorx",
|
||||
"authorUrl": "https://github.com/reorx",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/* src/styles.css */
|
||||
:root {
|
||||
--shadow-color: 0deg 0% 0%;
|
||||
--shadow-elevation-medium:
|
||||
0.5px 0.5px 0.7px hsl(var(--shadow-color) / 0.14),
|
||||
1.1px 1.1px 1.5px -0.9px hsl(var(--shadow-color) / 0.12),
|
||||
2.4px 2.5px 3.3px -1.8px hsl(var(--shadow-color) / 0.1),
|
||||
5.3px 5.6px 7.3px -2.7px hsl(var(--shadow-color) / 0.09),
|
||||
11px 11.4px 15.1px -3.6px hsl(var(--shadow-color) / 0.07);
|
||||
}
|
||||
.image-rename-modal .modal {
|
||||
width: 65%;
|
||||
min-width: 600px;
|
||||
}
|
||||
.image-rename-modal .modal-content {
|
||||
padding: 10px 5px;
|
||||
}
|
||||
.image-rename-modal .image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.image-rename-modal .info {
|
||||
padding: 10px 0;
|
||||
color: var(--text-muted);
|
||||
user-select: text;
|
||||
}
|
||||
.image-rename-modal .info li > span:nth-of-type(1) {
|
||||
display: inline-block;
|
||||
width: 6em;
|
||||
margin-right: .5em;
|
||||
}
|
||||
.image-rename-modal .info li > span:nth-of-type(1):after {
|
||||
content: ":";
|
||||
float: right;
|
||||
}
|
||||
.image-rename-modal .image-container img {
|
||||
display: block;
|
||||
max-height: 300px;
|
||||
box-shadow: var(--shadow-elevation-medium);
|
||||
}
|
||||
.image-rename-modal .setting-item-control input {
|
||||
min-width: 300px;
|
||||
}
|
||||
.image-rename-modal .error {
|
||||
border: 1px solid rgb(201, 90, 90);
|
||||
color: rgb(134, 22, 22);
|
||||
padding: 10px;
|
||||
}
|
||||
.image-rename-modal table {
|
||||
font-size: .9em;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 1.5em;
|
||||
user-select: text;
|
||||
}
|
||||
.image-rename-modal table td {
|
||||
padding-right: 1em;
|
||||
}
|
||||
.image-rename-modal table thead td {
|
||||
font-weight: 700;
|
||||
}
|
||||
.image-rename-modal table tbody td .file-path {
|
||||
font-size: .8em;
|
||||
color: var(--text-faint);
|
||||
line-height: 1;
|
||||
}
|
||||
.long-description-setting-item {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.long-description-setting-item .setting-item-description {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.long-description-setting-item .setting-item-control {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.long-description-setting-item .setting-item-control input {
|
||||
min-width: 300px;
|
||||
width: 50%;
|
||||
}
|
||||
2
.obsidian/themes/Blue Topaz/manifest.json
vendored
2
.obsidian/themes/Blue Topaz/manifest.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Blue Topaz",
|
||||
"version": "2023112601",
|
||||
"version": "2024121301",
|
||||
"minAppVersion": "1.0.0",
|
||||
"author": "WhyI & Pkmer",
|
||||
"authorUrl": "https://github.com/whyt-byte"
|
||||
|
||||
4074
.obsidian/themes/Blue Topaz/theme.css
vendored
4074
.obsidian/themes/Blue Topaz/theme.css
vendored
File diff suppressed because one or more lines are too long
13
.obsidian/themes/Shimmering Focus/manifest.json
vendored
13
.obsidian/themes/Shimmering Focus/manifest.json
vendored
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"name": "Shimmering Focus",
|
||||
"version": "3.599",
|
||||
"minAppVersion": "1.3.1",
|
||||
"author": "pseudometa",
|
||||
"fundingUrl": "https://ko-fi.com/pseudometa",
|
||||
"author": "pseudometa aka Chris Grieser",
|
||||
"version": "5.27",
|
||||
"minAppVersion": "1.6.0",
|
||||
"authorUrl": "https://github.com/chrisgrieser/shimmering-focus",
|
||||
"helpUrl": "https://github.com/chrisgrieser/shimmering-focus#readme"
|
||||
"helpUrl": "https://github.com/chrisgrieser/shimmering-focus#readme",
|
||||
"fundingUrl": {
|
||||
"Ko-Fi": "https://ko-fi.com/pseudometa",
|
||||
"PayPal": "https://www.paypal.me/ChrisGrieser"
|
||||
}
|
||||
}
|
||||
|
||||
6575
.obsidian/themes/Shimmering Focus/theme.css
vendored
6575
.obsidian/themes/Shimmering Focus/theme.css
vendored
File diff suppressed because one or more lines are too long
2
.obsidian/themes/Things/manifest.json
vendored
2
.obsidian/themes/Things/manifest.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Things",
|
||||
"version": "2.1.19",
|
||||
"version": "2.1.20",
|
||||
"minAppVersion": "1.0.0",
|
||||
"author": "@colineckert",
|
||||
"authorUrl": "https://twitter.com/colineckert"
|
||||
|
||||
119
.obsidian/themes/Things/theme.css
vendored
119
.obsidian/themes/Things/theme.css
vendored
@@ -1,6 +1,6 @@
|
||||
/*───────────────────────────────────────────────────────
|
||||
THINGS
|
||||
Version 2.1.19
|
||||
Version 2.1.20
|
||||
Created by @colineckert
|
||||
|
||||
Readme:
|
||||
@@ -54,6 +54,12 @@ body {
|
||||
--highlight-background-color-underline: hsl(50deg 100% 50% / 100%) !important;
|
||||
--highlight-background-color--active: hsl(50deg 100% 50% / 20%) !important;
|
||||
|
||||
--progress-color-1: #ad5758;
|
||||
--progress-color-2: #b87f4c;
|
||||
--progress-color-3: #d2b874;
|
||||
--progress-color-4: #b0c07e;
|
||||
--progress-color-5: #768399;
|
||||
|
||||
/* Font families */
|
||||
--font-text-theme: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Inter, Ubuntu, sans-serif;
|
||||
@@ -334,15 +340,6 @@ body:not(.default-font-color) mark em {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* Highlight styling */
|
||||
span.cm-highlight,
|
||||
.markdown-preview-view mark,
|
||||
span.search-result-file-matched-text {
|
||||
padding: 0.05em 0;
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
}
|
||||
|
||||
/* Fancy highlight */
|
||||
body.fancy-highlight span.cm-highlight,
|
||||
body.fancy-highlight .markdown-preview-view mark,
|
||||
@@ -962,6 +959,50 @@ body:not(.tasks) li[data-task='D'].task-list-item.is-checked {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* ------------------- */
|
||||
/* Progress bars styling. Credit Minimal theme: https://minimal.guide/progress-bars */
|
||||
/* Support @kepano - https://www.buymeacoffee.com/kepano */
|
||||
/* ------------------- */
|
||||
.progress-color {
|
||||
.markdown-rendered progress,
|
||||
.markdown-source-view.is-live-preview progress,
|
||||
.markdown-preview-view progress {
|
||||
&[value^='1']::-webkit-progress-value,
|
||||
&[value^='2']::-webkit-progress-value,
|
||||
&[value^='3']::-webkit-progress-value {
|
||||
background-color: var(--progress-color-1);
|
||||
}
|
||||
&[value^='4']::-webkit-progress-value,
|
||||
&[value^='5']::-webkit-progress-value {
|
||||
background-color: var(--progress-color-2);
|
||||
}
|
||||
&[value^='6']::-webkit-progress-value,
|
||||
&[value^='7']::-webkit-progress-value {
|
||||
background-color: var(--progress-color-3);
|
||||
}
|
||||
&[value^='8']::-webkit-progress-value,
|
||||
&[value^='9']::-webkit-progress-value {
|
||||
background-color: var(--progress-color-4);
|
||||
}
|
||||
&[value='1']::-webkit-progress-value,
|
||||
&[value='100']::-webkit-progress-value {
|
||||
background-color: var(--progress-color-5);
|
||||
}
|
||||
|
||||
&[value='0']::-webkit-progress-value,
|
||||
&[value='2']::-webkit-progress-value,
|
||||
&[value='3']::-webkit-progress-value,
|
||||
&[value='4']::-webkit-progress-value,
|
||||
&[value='5']::-webkit-progress-value,
|
||||
&[value='6']::-webkit-progress-value,
|
||||
&[value='7']::-webkit-progress-value,
|
||||
&[value='8']::-webkit-progress-value,
|
||||
&[value='9']::-webkit-progress-value {
|
||||
background-color: var(--progress-color-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────── */
|
||||
/* Plugins */
|
||||
/* ─────────────────────────────────────────────────── */
|
||||
@@ -1161,6 +1202,23 @@ body:not(.no-kanban-styles)
|
||||
> textarea {
|
||||
background: transparent;
|
||||
}
|
||||
body .kanban-plugin__item-button-wrapper > button {
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
background: 0 0;
|
||||
min-height: calc(var(--input-height) + 8px);
|
||||
}
|
||||
body .kanban-plugin__item-form .kanban-plugin__item-input-wrapper {
|
||||
min-height: calc(var(--input-height) + 8px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.kanban-plugin__item-input-wrapper textarea {
|
||||
background-color: var(--background-primary);
|
||||
}
|
||||
.kanban-plugin__lane-items {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────── */
|
||||
/* Styles Settings */
|
||||
@@ -1308,6 +1366,47 @@ settings:
|
||||
type: variable-color
|
||||
format: hex
|
||||
default: '#FFFFFF'
|
||||
-
|
||||
id: progress-color
|
||||
title: Progress colorful mode switcher
|
||||
description: Toggle progress color scheme
|
||||
type: class-toggle
|
||||
default: false
|
||||
-
|
||||
id: progress-color-1
|
||||
title: progress 2-39% color
|
||||
type: variable-color
|
||||
opacity: true
|
||||
format: hex
|
||||
default: '#ad5758'
|
||||
-
|
||||
id: progress-color-2
|
||||
title: progress 40-59% color
|
||||
type: variable-color
|
||||
opacity: true
|
||||
format: hex
|
||||
default: '#b87f4c'
|
||||
-
|
||||
id: progress-color-3
|
||||
title: progress 60-79% color
|
||||
type: variable-color
|
||||
opacity: true
|
||||
format: hex
|
||||
default: '#d2b874'
|
||||
-
|
||||
id: progress-color-4
|
||||
title: progress 80-99% color
|
||||
type: variable-color
|
||||
opacity: true
|
||||
format: hex
|
||||
default: '#b0c07e'
|
||||
-
|
||||
id: progress-color-5
|
||||
title: progress 1,100% color
|
||||
type: variable-color
|
||||
opacity: true
|
||||
format: hex
|
||||
default: '#768399'
|
||||
-
|
||||
id: headings
|
||||
title: Headings
|
||||
|
||||
2
.obsidian/zk-prefixer.json
vendored
2
.obsidian/zk-prefixer.json
vendored
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"template": "98. templates/front matter"
|
||||
"template": ""
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
date: 2025-03-03
|
||||
time: 23:24:28
|
||||
description:
|
||||
---
|
||||
|
||||
時間:23:24:28
|
||||
|
||||
---
|
||||
|
||||
# 今日發生什麼事?
|
||||
|
||||
|
||||
# 有什麼想法?
|
||||
|
||||
|
||||
# 相對應的行動是什麼?
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
date: 2025-03-04
|
||||
time: 23:22:45
|
||||
description:
|
||||
---
|
||||
|
||||
時間:23:22:45
|
||||
|
||||
---
|
||||
|
||||
# 今日發生什麼事?
|
||||
|
||||
|
||||
# 有什麼想法?
|
||||
|
||||
|
||||
# 相對應的行動是什麼?
|
||||
|
||||
Reference in New Issue
Block a user