vault backup: 2024-01-02 14:46:20
This commit is contained in:
6
.obsidian/appearance.json
vendored
6
.obsidian/appearance.json
vendored
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"theme": "moonstone",
|
"theme": "moonstone",
|
||||||
"cssTheme": "Blue Topaz",
|
"cssTheme": "",
|
||||||
"baseFontSize": 13,
|
"baseFontSize": 13,
|
||||||
"translucency": false,
|
"translucency": false,
|
||||||
"enabledCssSnippets": [],
|
"enabledCssSnippets": [
|
||||||
|
"obsidian"
|
||||||
|
],
|
||||||
"baseFontSizeAction": true,
|
"baseFontSizeAction": true,
|
||||||
"interfaceFontFamily": "",
|
"interfaceFontFamily": "",
|
||||||
"textFontFamily": "Verdana,Microsoft YaHei UI",
|
"textFontFamily": "Verdana,Microsoft YaHei UI",
|
||||||
|
|||||||
3
.obsidian/community-plugins.json
vendored
3
.obsidian/community-plugins.json
vendored
@@ -9,5 +9,6 @@
|
|||||||
"periodic-notes",
|
"periodic-notes",
|
||||||
"obsidian-git",
|
"obsidian-git",
|
||||||
"obsidian-quiet-outline",
|
"obsidian-quiet-outline",
|
||||||
"obsidian-tracker"
|
"obsidian-tracker",
|
||||||
|
"obsidian-paste-image-rename"
|
||||||
]
|
]
|
||||||
942
.obsidian/plugins/obsidian-paste-image-rename/main.js
vendored
Normal file
942
.obsidian/plugins/obsidian-paste-image-rename/main.js
vendored
Normal file
@@ -0,0 +1,942 @@
|
|||||||
|
/* 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();
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
10
.obsidian/plugins/obsidian-paste-image-rename/manifest.json
vendored
Normal file
10
.obsidian/plugins/obsidian-paste-image-rename/manifest.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
79
.obsidian/plugins/obsidian-paste-image-rename/styles.css
vendored
Normal file
79
.obsidian/plugins/obsidian-paste-image-rename/styles.css
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/* 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%;
|
||||||
|
}
|
||||||
41
.obsidian/workspace.json
vendored
41
.obsidian/workspace.json
vendored
@@ -39,14 +39,11 @@
|
|||||||
"id": "069e76e47c814366",
|
"id": "069e76e47c814366",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"state": {
|
"state": {
|
||||||
"type": "canvas",
|
"type": "markdown",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas",
|
"file": "02. 個人:Daily/2024-01-02(週二).md",
|
||||||
"viewState": {
|
"mode": "source",
|
||||||
"x": -76.5,
|
"source": true
|
||||||
"y": 29,
|
|
||||||
"zoom": -0.5
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +77,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "search",
|
"type": "search",
|
||||||
"state": {
|
"state": {
|
||||||
"query": "ffmpeg",
|
"query": "[\"tags\"]",
|
||||||
"matchingCase": false,
|
"matchingCase": false,
|
||||||
"explainSearch": false,
|
"explainSearch": false,
|
||||||
"collapseAll": false,
|
"collapseAll": false,
|
||||||
@@ -97,7 +94,8 @@
|
|||||||
"state": {}
|
"state": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"currentTab": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
@@ -117,7 +115,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "backlink",
|
"type": "backlink",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas",
|
"file": "02. 個人:Daily/2024-01-02(週二).md",
|
||||||
"collapseAll": false,
|
"collapseAll": false,
|
||||||
"extraContext": false,
|
"extraContext": false,
|
||||||
"sortOrder": "alphabetical",
|
"sortOrder": "alphabetical",
|
||||||
@@ -142,7 +140,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "outline",
|
"type": "outline",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas"
|
"file": "02. 個人:Daily/2024-01-02(週二).md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -172,12 +170,12 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "file-properties",
|
"type": "file-properties",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas"
|
"file": "02. 個人:Daily/2024-01-02(週二).md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"currentTab": 1
|
"currentTab": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ae4bf98badbfc7ee",
|
"id": "ae4bf98badbfc7ee",
|
||||||
@@ -215,10 +213,16 @@
|
|||||||
"periodic-notes:Open today": false
|
"periodic-notes:Open today": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "069e76e47c814366",
|
"active": "bad194a4534ef74b",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
"05. 資料收集/布萊茲‧帕斯卡(Blaise Pascal).md",
|
"02. 個人:Daily/2024-01-02(週二)——.md",
|
||||||
|
"02. 個人:Daily/2024-01-02(週二).md",
|
||||||
|
"98. templates/日記.md",
|
||||||
|
"attachments/wakatime_2023_weekdays.png",
|
||||||
|
"attachments/wakatime_2023_days.png",
|
||||||
|
"attachments/edc8488a7547573ac02d8ab1afec473553bffdce.png",
|
||||||
"05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas",
|
"05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas",
|
||||||
|
"05. 資料收集/布萊茲‧帕斯卡(Blaise Pascal).md",
|
||||||
"00. Inbox/Habit Tracker.md",
|
"00. Inbox/Habit Tracker.md",
|
||||||
"05. 資料收集/核心肌群訓練.md",
|
"05. 資料收集/核心肌群訓練.md",
|
||||||
"05. 資料收集/時間不一致性.md",
|
"05. 資料收集/時間不一致性.md",
|
||||||
@@ -243,8 +247,6 @@
|
|||||||
"05. 資料收集/04. Programming/FFMPEG/00. Introduction.md",
|
"05. 資料收集/04. Programming/FFMPEG/00. Introduction.md",
|
||||||
"05. 資料收集/04. Programming/FFMPEG/01. Setup.md",
|
"05. 資料收集/04. Programming/FFMPEG/01. Setup.md",
|
||||||
"05. 資料收集/04. Programming/FFMPEG/AV_CODEC_FLAG_GLOBAL_HEADER.md",
|
"05. 資料收集/04. Programming/FFMPEG/AV_CODEC_FLAG_GLOBAL_HEADER.md",
|
||||||
"05. 資料收集/04. Programming/FFMPEG/Profiles & levels.md",
|
|
||||||
"05. 資料收集/04. Programming/FFMPEG/av_opt_set.md",
|
|
||||||
"attachments/Pasted image 20231225164717.png",
|
"attachments/Pasted image 20231225164717.png",
|
||||||
"attachments/Pasted image 20231225164413.png",
|
"attachments/Pasted image 20231225164413.png",
|
||||||
"attachments/Pasted image 20231225164349.png",
|
"attachments/Pasted image 20231225164349.png",
|
||||||
@@ -263,9 +265,6 @@
|
|||||||
"00. Inbox/My Mindmap.canvas",
|
"00. Inbox/My Mindmap.canvas",
|
||||||
"05. 資料收集/Tool Setup/Software/diskstation/share/Tools/字型",
|
"05. 資料收集/Tool Setup/Software/diskstation/share/Tools/字型",
|
||||||
"05. 資料收集/Tool Setup/Software/diskstation/share/Tools",
|
"05. 資料收集/Tool Setup/Software/diskstation/share/Tools",
|
||||||
"05. 資料收集/Tool Setup/Software/diskstation/share",
|
"05. 資料收集/Tool Setup/Software/diskstation/share"
|
||||||
"attachments/Pasted image 20230504183452.png",
|
|
||||||
"attachments/Pasted image 20230504183439.png",
|
|
||||||
"attachments/Pasted image 20230426214323.png"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
25
02. 個人:Daily/2024-01-02(週二).md
Normal file
25
02. 個人:Daily/2024-01-02(週二).md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
tags: wakatime, 年度回顧, 2023
|
||||||
|
aliases:
|
||||||
|
date: 2024-01-02
|
||||||
|
time: 14:42:28
|
||||||
|
description:
|
||||||
|
---
|
||||||
|
|
||||||
|
時間:14:42:28
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 今日發生什麼事?
|
||||||
|
## Wakatime
|
||||||
|
今天看到Wakatime的年度回顧,覺得很有趣,所以紀錄一下。
|
||||||
|
![[edc8488a7547573ac02d8ab1afec473553bffdce.png]]
|
||||||
|
![[wakatime_2023_days.png]]
|
||||||
|
![[wakatime_2023_weekdays.png]]
|
||||||
|
|
||||||
|
|
||||||
|
# 有什麼想法?
|
||||||
|
|
||||||
|
|
||||||
|
# 相對應的行動是什麼?
|
||||||
|
|
||||||
@@ -4,28 +4,17 @@ aliases:
|
|||||||
date: {{date}}
|
date: {{date}}
|
||||||
time: {{time:HH:mm:ss}}
|
time: {{time:HH:mm:ss}}
|
||||||
description:
|
description:
|
||||||
anki: 0
|
|
||||||
readingSkill: 0
|
|
||||||
readingCasual: 0
|
|
||||||
啞鈴: 0
|
|
||||||
伏地挺身: 0
|
|
||||||
跑步: 0
|
|
||||||
---
|
---
|
||||||
|
|
||||||
時間:{{time:HH:mm:ss}}
|
時間:{{time:HH:mm:ss}}
|
||||||
|
|
||||||
### TAG
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 新增TODO
|
# 今日發生什麼事?
|
||||||
#### 私事
|
|
||||||
|
|
||||||
|
|
||||||
#### 公事
|
# 有什麼想法?
|
||||||
|
|
||||||
|
|
||||||
---
|
# 相對應的行動是什麼?
|
||||||
|
|
||||||
### 今日回顧
|
|
||||||
|
|||||||
BIN
attachments/edc8488a7547573ac02d8ab1afec473553bffdce.png
Normal file
BIN
attachments/edc8488a7547573ac02d8ab1afec473553bffdce.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
attachments/wakatime_2023_days.png
Normal file
BIN
attachments/wakatime_2023_days.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
attachments/wakatime_2023_weekdays.png
Normal file
BIN
attachments/wakatime_2023_weekdays.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
Reference in New Issue
Block a user