diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json index 72e6a15..8e8fab7 100644 --- a/.obsidian/appearance.json +++ b/.obsidian/appearance.json @@ -1,9 +1,11 @@ { "theme": "moonstone", - "cssTheme": "Blue Topaz", + "cssTheme": "", "baseFontSize": 13, "translucency": false, - "enabledCssSnippets": [], + "enabledCssSnippets": [ + "obsidian" + ], "baseFontSizeAction": true, "interfaceFontFamily": "", "textFontFamily": "Verdana,Microsoft YaHei UI", diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json index a247921..89f3c01 100644 --- a/.obsidian/community-plugins.json +++ b/.obsidian/community-plugins.json @@ -9,5 +9,6 @@ "periodic-notes", "obsidian-git", "obsidian-quiet-outline", - "obsidian-tracker" + "obsidian-tracker", + "obsidian-paste-image-rename" ] \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-paste-image-rename/main.js b/.obsidian/plugins/obsidian-paste-image-rename/main.js new file mode 100644 index 0000000..9a0fa6d --- /dev/null +++ b/.obsidian/plugins/obsidian-paste-image-rename/main.js @@ -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( + `^(?\\d+)${delimiterEscaped}(?${newNameStemEscaped})\\.${newNameExt}$` + ); + } else { + dupNameRegex = new RegExp( + `^(?${newNameStemEscaped})${delimiterEscaped}(?\\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(); + }) + )); + } +}; diff --git a/.obsidian/plugins/obsidian-paste-image-rename/manifest.json b/.obsidian/plugins/obsidian-paste-image-rename/manifest.json new file mode 100644 index 0000000..152d913 --- /dev/null +++ b/.obsidian/plugins/obsidian-paste-image-rename/manifest.json @@ -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 +} \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-paste-image-rename/styles.css b/.obsidian/plugins/obsidian-paste-image-rename/styles.css new file mode 100644 index 0000000..d542d56 --- /dev/null +++ b/.obsidian/plugins/obsidian-paste-image-rename/styles.css @@ -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%; +} diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 909ac7c..d0e454e 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -39,14 +39,11 @@ "id": "069e76e47c814366", "type": "leaf", "state": { - "type": "canvas", + "type": "markdown", "state": { - "file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas", - "viewState": { - "x": -76.5, - "y": 29, - "zoom": -0.5 - } + "file": "02. 個人:Daily/2024-01-02(週二).md", + "mode": "source", + "source": true } } } @@ -80,7 +77,7 @@ "state": { "type": "search", "state": { - "query": "ffmpeg", + "query": "[\"tags\"]", "matchingCase": false, "explainSearch": false, "collapseAll": false, @@ -97,7 +94,8 @@ "state": {} } } - ] + ], + "currentTab": 1 } ], "direction": "horizontal", @@ -117,7 +115,7 @@ "state": { "type": "backlink", "state": { - "file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas", + "file": "02. 個人:Daily/2024-01-02(週二).md", "collapseAll": false, "extraContext": false, "sortOrder": "alphabetical", @@ -142,7 +140,7 @@ "state": { "type": "outline", "state": { - "file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas" + "file": "02. 個人:Daily/2024-01-02(週二).md" } } }, @@ -172,12 +170,12 @@ "state": { "type": "file-properties", "state": { - "file": "05. 資料收集/讀書筆記/20240101 - 筆記的魔力.canvas" + "file": "02. 個人:Daily/2024-01-02(週二).md" } } } ], - "currentTab": 1 + "currentTab": 4 }, { "id": "ae4bf98badbfc7ee", @@ -215,10 +213,16 @@ "periodic-notes:Open today": false } }, - "active": "069e76e47c814366", + "active": "bad194a4534ef74b", "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. 資料收集/布萊茲‧帕斯卡(Blaise Pascal).md", "00. Inbox/Habit Tracker.md", "05. 資料收集/核心肌群訓練.md", "05. 資料收集/時間不一致性.md", @@ -243,8 +247,6 @@ "05. 資料收集/04. Programming/FFMPEG/00. Introduction.md", "05. 資料收集/04. Programming/FFMPEG/01. Setup.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 20231225164413.png", "attachments/Pasted image 20231225164349.png", @@ -263,9 +265,6 @@ "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", - "attachments/Pasted image 20230504183452.png", - "attachments/Pasted image 20230504183439.png", - "attachments/Pasted image 20230426214323.png" + "05. 資料收集/Tool Setup/Software/diskstation/share" ] } \ No newline at end of file diff --git a/02. 個人:Daily/2024-01-02(週二).md b/02. 個人:Daily/2024-01-02(週二).md new file mode 100644 index 0000000..84afb2d --- /dev/null +++ b/02. 個人:Daily/2024-01-02(週二).md @@ -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]] + + +# 有什麼想法? + + +# 相對應的行動是什麼? + diff --git a/98. templates/日記.md b/98. templates/日記.md index 9527044..3365a25 100644 --- a/98. templates/日記.md +++ b/98. templates/日記.md @@ -4,28 +4,17 @@ aliases: date: {{date}} time: {{time:HH:mm:ss}} description: -anki: 0 -readingSkill: 0 -readingCasual: 0 -啞鈴: 0 -伏地挺身: 0 -跑步: 0 --- 時間:{{time:HH:mm:ss}} -### TAG - - --- -### 新增TODO -#### 私事 +# 今日發生什麼事? -#### 公事 +# 有什麼想法? ---- +# 相對應的行動是什麼? -### 今日回顧 diff --git a/attachments/edc8488a7547573ac02d8ab1afec473553bffdce.png b/attachments/edc8488a7547573ac02d8ab1afec473553bffdce.png new file mode 100644 index 0000000..33fa4d6 Binary files /dev/null and b/attachments/edc8488a7547573ac02d8ab1afec473553bffdce.png differ diff --git a/attachments/wakatime_2023_days.png b/attachments/wakatime_2023_days.png new file mode 100644 index 0000000..3e49437 Binary files /dev/null and b/attachments/wakatime_2023_days.png differ diff --git a/attachments/wakatime_2023_weekdays.png b/attachments/wakatime_2023_weekdays.png new file mode 100644 index 0000000..2acb132 Binary files /dev/null and b/attachments/wakatime_2023_weekdays.png differ