943 lines
32 KiB
JavaScript
943 lines
32 KiB
JavaScript
/* 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();
|
|
})
|
|
));
|
|
}
|
|
};
|