1774707388

This commit is contained in:
Docker7530
2026-03-28 22:16:30 +08:00
parent d17550351d
commit 798019bbbf
34 changed files with 65 additions and 1469 deletions
+1 -3
View File
@@ -22,7 +22,5 @@
"showIndentGuide": false, "showIndentGuide": false,
"spellcheck": false, "spellcheck": false,
"foldIndent": false, "foldIndent": false,
"userIgnoreFilters": [ "userIgnoreFilters": null
"attachment/"
]
} }
+1 -3
View File
@@ -1,7 +1,5 @@
{ {
"enabledCssSnippets": [ "enabledCssSnippets": [],
"myTheme"
],
"monospaceFontFamily": "JetBrainsMono Nerd Font Mono", "monospaceFontFamily": "JetBrainsMono Nerd Font Mono",
"textFontFamily": "思源黑体", "textFontFamily": "思源黑体",
"interfaceFontFamily": "JetBrainsMono Nerd Font Mono", "interfaceFontFamily": "JetBrainsMono Nerd Font Mono",
+1 -3
View File
@@ -5,7 +5,5 @@
"oz-clear-unused-images", "oz-clear-unused-images",
"obsidian-excalidraw-plugin", "obsidian-excalidraw-plugin",
"templater-obsidian", "templater-obsidian",
"obsidian-linter", "obsidian-linter"
"obsidian-paste-image-rename",
"recent-files-obsidian"
] ]
-10
View File
@@ -1,10 +0,0 @@
{
"imageNamePattern": "image-{{DATE:YYYYMMDDHHmmssSSS}}",
"dupNumberAtStart": false,
"dupNumberDelimiter": "-",
"dupNumberAlways": false,
"autoRename": true,
"handleAllAttachments": false,
"excludeExtensionPattern": "",
"disableRenameNotice": true
}
-942
View File
@@ -1,942 +0,0 @@
/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD */
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// package.json
var require_package = __commonJS({
"package.json"(exports, module2) {
module2.exports = {
name: "obsidian-paste-image-rename",
version: "1.6.1",
main: "main.js",
scripts: {
start: "node esbuild.config.mjs",
build: "tsc -noEmit -skipLibCheck && BUILD_ENV=production node esbuild.config.mjs && cp manifest.json build",
version: "node version-bump.mjs && git add manifest.json versions.json",
release: "npm run build && gh release create ${npm_package_version} build/*"
},
keywords: [],
author: "Reorx",
license: "MIT",
devDependencies: {
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"builtin-modules": "^3.3.0",
esbuild: "0.16.17",
obsidian: "^1.1.1",
tslib: "2.5.0",
typescript: "4.9.4"
},
dependencies: {
"cash-dom": "^8.1.2"
}
};
}
});
// src/main.ts
var main_exports = {};
__export(main_exports, {
default: () => PasteImageRenamePlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian2 = require("obsidian");
// src/batch.ts
var import_obsidian = require("obsidian");
// src/utils.ts
var DEBUG = false;
if (DEBUG)
console.log("DEBUG is enabled");
function debugLog(...args) {
if (DEBUG) {
console.log(new Date().toISOString().slice(11, 23), ...args);
}
}
function createElementTree(rootEl, opts) {
const result = {
el: rootEl.createEl(opts.tag, opts),
children: []
};
const children = opts.children || [];
for (const child of children) {
result.children.push(createElementTree(result.el, child));
}
return result;
}
var path = {
// Credit: @creationix/path.js
join(...partSegments) {
let parts = [];
for (let i = 0, l = partSegments.length; i < l; i++) {
parts = parts.concat(partSegments[i].split("/"));
}
const newParts = [];
for (let i = 0, l = parts.length; i < l; i++) {
const part = parts[i];
if (!part || part === ".")
continue;
else
newParts.push(part);
}
if (parts[0] === "")
newParts.unshift("");
return newParts.join("/");
},
// returns the last part of a path, e.g. 'foo.jpg'
basename(fullpath) {
const sp = fullpath.split("/");
return sp[sp.length - 1];
},
// return extension without dot, e.g. 'jpg'
extension(fullpath) {
const positions = [...fullpath.matchAll(new RegExp("\\.", "gi"))].map((a) => a.index);
return fullpath.slice(positions[positions.length - 1] + 1);
}
};
var filenameNotAllowedChars = /[^\p{L}0-9~`!@$&*()\-_=+{};'",<.>? ]/ug;
var sanitizer = {
filename(s) {
return s.replace(filenameNotAllowedChars, "").trim();
},
delimiter(s) {
s = this.filename(s);
if (!s)
s = "-";
return s;
}
};
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function lockInputMethodComposition(el) {
const state = {
lock: false
};
el.addEventListener("compositionstart", () => {
state.lock = true;
});
el.addEventListener("compositionend", () => {
state.lock = false;
});
return state;
}
// src/batch.ts
var ImageBatchRenameModal = class extends import_obsidian.Modal {
constructor(app, activeFile, renameFunc, onClose) {
super(app);
this.activeFile = activeFile;
this.renameFunc = renameFunc;
this.onCloseExtra = onClose;
this.state = {
namePattern: "",
extPattern: "",
nameReplace: "",
renameTasks: []
};
}
onOpen() {
this.containerEl.addClass("image-rename-modal");
const { contentEl, titleEl } = this;
titleEl.setText("Batch rename embeded files");
const namePatternSetting = new import_obsidian.Setting(contentEl).setName("Name pattern").setDesc("Please input the name pattern to match files (regex)").addText((text) => text.setValue(this.state.namePattern).onChange(
(value) => __async(this, null, function* () {
this.state.namePattern = value;
})
));
const npInputEl = namePatternSetting.controlEl.children[0];
npInputEl.focus();
const npInputState = lockInputMethodComposition(npInputEl);
npInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !npInputState.lock) {
e.preventDefault();
if (!this.state.namePattern) {
errorEl.innerText = 'Error: "Name pattern" could not be empty';
errorEl.style.display = "block";
return;
}
this.matchImageNames(tbodyEl);
}
}));
const extPatternSetting = new import_obsidian.Setting(contentEl).setName("Extension pattern").setDesc("Please input the extension pattern to match files (regex)").addText((text) => text.setValue(this.state.extPattern).onChange(
(value) => __async(this, null, function* () {
this.state.extPattern = value;
})
));
const extInputEl = extPatternSetting.controlEl.children[0];
extInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter") {
e.preventDefault();
this.matchImageNames(tbodyEl);
}
}));
const nameReplaceSetting = new import_obsidian.Setting(contentEl).setName("Name replace").setDesc("Please input the string to replace the matched name (use $1, $2 for regex groups)").addText((text) => text.setValue(this.state.nameReplace).onChange(
(value) => __async(this, null, function* () {
this.state.nameReplace = value;
})
));
const nrInputEl = nameReplaceSetting.controlEl.children[0];
const nrInputState = lockInputMethodComposition(nrInputEl);
nrInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !nrInputState.lock) {
e.preventDefault();
this.matchImageNames(tbodyEl);
}
}));
const matchedContainer = contentEl.createDiv({
cls: "matched-container"
});
const tableET = createElementTree(matchedContainer, {
tag: "table",
children: [
{
tag: "thead",
children: [
{
tag: "tr",
children: [
{
tag: "td",
text: "Original path"
},
{
tag: "td",
text: "Renamed Name"
}
]
}
]
},
{
tag: "tbody"
}
]
});
const tbodyEl = tableET.children[1].el;
const errorEl = contentEl.createDiv({
cls: "error",
attr: {
style: "display: none;"
}
});
new import_obsidian.Setting(contentEl).addButton((button) => {
button.setButtonText("Rename all").setClass("mod-cta").onClick(() => {
new ConfirmModal(
this.app,
"Confirm rename all",
`Are you sure? This will rename all the ${this.state.renameTasks.length} images matched the pattern.`,
() => {
this.renameAll();
this.close();
}
).open();
});
}).addButton((button) => {
button.setButtonText("Cancel").onClick(() => {
this.close();
});
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.onCloseExtra();
}
renameAll() {
return __async(this, null, function* () {
debugLog("renameAll", this.state);
for (const task of this.state.renameTasks) {
yield this.renameFunc(task.file, task.name);
}
});
}
matchImageNames(tbodyEl) {
const { state } = this;
const renameTasks = [];
tbodyEl.empty();
const fileCache = this.app.metadataCache.getFileCache(this.activeFile);
if (!fileCache || !fileCache.embeds)
return;
const namePatternRegex = new RegExp(state.namePattern, "g");
const extPatternRegex = new RegExp(state.extPattern);
fileCache.embeds.forEach((embed) => {
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, this.activeFile.path);
if (!file) {
console.warn("file not found", embed.link);
return;
}
if (state.extPattern) {
const m0 = extPatternRegex.exec(file.extension);
if (!m0)
return;
}
const stem = file.basename;
namePatternRegex.lastIndex = 0;
const m1 = namePatternRegex.exec(stem);
if (!m1)
return;
let renamedName = file.name;
if (state.nameReplace) {
namePatternRegex.lastIndex = 0;
renamedName = stem.replace(namePatternRegex, state.nameReplace);
renamedName = `${renamedName}.${file.extension}`;
}
renameTasks.push({
file,
name: renamedName
});
createElementTree(tbodyEl, {
tag: "tr",
children: [
{
tag: "td",
children: [
{
tag: "span",
text: file.name
},
{
tag: "div",
text: file.path,
attr: {
class: "file-path"
}
}
]
},
{
tag: "td",
children: [
{
tag: "span",
text: renamedName
},
{
tag: "div",
text: path.join(file.parent.path, renamedName),
attr: {
class: "file-path"
}
}
]
}
]
});
});
debugLog("new renameTasks", renameTasks);
state.renameTasks = renameTasks;
}
};
var ConfirmModal = class extends import_obsidian.Modal {
constructor(app, title, message, onConfirm) {
super(app);
this.title = title;
this.message = message;
this.onConfirm = onConfirm;
}
onOpen() {
const { contentEl, titleEl } = this;
titleEl.setText(this.title);
contentEl.createEl("p", {
text: this.message
});
new import_obsidian.Setting(contentEl).addButton((button) => {
button.setButtonText("Yes").setClass("mod-warning").onClick(() => {
this.onConfirm();
this.close();
});
}).addButton((button) => {
button.setButtonText("No").onClick(() => {
this.close();
});
});
}
};
// src/template.ts
var dateTmplRegex = /{{DATE:([^}]+)}}/gm;
var frontmatterTmplRegex = /{{frontmatter:([^}]+)}}/gm;
var replaceDateVar = (s, date) => {
const m = dateTmplRegex.exec(s);
if (!m)
return s;
return s.replace(m[0], date.format(m[1]));
};
var replaceFrontmatterVar = (s, frontmatter) => {
if (!frontmatter)
return s;
const m = frontmatterTmplRegex.exec(s);
if (!m)
return s;
return s.replace(m[0], frontmatter[m[1]] || "");
};
var renderTemplate = (tmpl, data, frontmatter) => {
const now = window.moment();
let text = tmpl;
let newtext;
while ((newtext = replaceDateVar(text, now)) != text) {
text = newtext;
}
while ((newtext = replaceFrontmatterVar(text, frontmatter)) != text) {
text = newtext;
}
text = text.replace(/{{imageNameKey}}/gm, data.imageNameKey).replace(/{{fileName}}/gm, data.fileName).replace(/{{dirName}}/gm, data.dirName).replace(/{{firstHeading}}/gm, data.firstHeading);
return text;
};
// src/main.ts
var DEFAULT_SETTINGS = {
imageNamePattern: "{{fileName}}",
dupNumberAtStart: false,
dupNumberDelimiter: "-",
dupNumberAlways: false,
autoRename: false,
handleAllAttachments: false,
excludeExtensionPattern: "",
disableRenameNotice: false
};
var PASTED_IMAGE_PREFIX = "Pasted image ";
var PasteImageRenamePlugin = class extends import_obsidian2.Plugin {
constructor() {
super(...arguments);
this.modals = [];
}
onload() {
return __async(this, null, function* () {
const pkg = require_package();
console.log(`Plugin loading: ${pkg.name} ${pkg.version} BUILD_ENV=${"production"}`);
yield this.loadSettings();
this.registerEvent(
this.app.vault.on("create", (file) => {
if (!(file instanceof import_obsidian2.TFile))
return;
const timeGapMs = new Date().getTime() - file.stat.ctime;
if (timeGapMs > 1e3)
return;
if (isMarkdownFile(file))
return;
if (isPastedImage(file)) {
debugLog("pasted image created", file);
this.startRenameProcess(file, this.settings.autoRename);
} else {
if (this.settings.handleAllAttachments) {
debugLog("handleAllAttachments for file", file);
if (this.testExcludeExtension(file)) {
debugLog("excluded file by ext", file);
return;
}
this.startRenameProcess(file, this.settings.autoRename);
}
}
})
);
const startBatchRenameProcess = () => {
this.openBatchRenameModal();
};
this.addCommand({
id: "batch-rename-embeded-files",
name: "Batch rename embeded files (in the current file)",
callback: startBatchRenameProcess
});
if (DEBUG) {
this.addRibbonIcon("wand-glyph", "Batch rename embeded files", startBatchRenameProcess);
}
const batchRenameAllImages = () => {
this.batchRenameAllImages();
};
this.addCommand({
id: "batch-rename-all-images",
name: "Batch rename all images instantly (in the current file)",
callback: batchRenameAllImages
});
if (DEBUG) {
this.addRibbonIcon("wand-glyph", "Batch rename all images instantly (in the current file)", batchRenameAllImages);
}
this.addSettingTab(new SettingTab(this.app, this));
});
}
startRenameProcess(file, autoRename = false) {
return __async(this, null, function* () {
const activeFile = this.getActiveFile();
if (!activeFile) {
new import_obsidian2.Notice("Error: No active file found.");
return;
}
const { stem, newName, isMeaningful } = this.generateNewName(file, activeFile);
debugLog("generated newName:", newName, isMeaningful);
if (!isMeaningful || !autoRename) {
this.openRenameModal(file, isMeaningful ? stem : "", activeFile.path);
return;
}
this.renameFile(file, newName, activeFile.path, true);
});
}
renameFile(file, inputNewName, sourcePath, replaceCurrentLine) {
return __async(this, null, function* () {
const { name: newName } = yield this.deduplicateNewName(inputNewName, file);
debugLog("deduplicated newName:", newName);
const originName = file.name;
const linkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
const newPath = path.join(file.parent.path, newName);
try {
yield this.app.fileManager.renameFile(file, newPath);
} catch (err) {
new import_obsidian2.Notice(`Failed to rename ${newName}: ${err}`);
throw err;
}
if (!replaceCurrentLine) {
return;
}
const newLinkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
debugLog("replace text", linkText, newLinkText);
const editor = this.getActiveEditor();
if (!editor) {
new import_obsidian2.Notice(`Failed to rename ${newName}: no active editor`);
return;
}
const cursor = editor.getCursor();
const line = editor.getLine(cursor.line);
const replacedLine = line.replace(linkText, newLinkText);
debugLog("current line -> replaced line", line, replacedLine);
editor.transaction({
changes: [
{
from: __spreadProps(__spreadValues({}, cursor), { ch: 0 }),
to: __spreadProps(__spreadValues({}, cursor), { ch: line.length }),
text: replacedLine
}
]
});
if (!this.settings.disableRenameNotice) {
new import_obsidian2.Notice(`Renamed ${originName} to ${newName}`);
}
});
}
openRenameModal(file, newName, sourcePath) {
const modal = new ImageRenameModal(
this.app,
file,
newName,
(confirmedName) => {
debugLog("confirmedName:", confirmedName);
this.renameFile(file, confirmedName, sourcePath, true);
},
() => {
this.modals.splice(this.modals.indexOf(modal), 1);
}
);
this.modals.push(modal);
modal.open();
debugLog("modals count", this.modals.length);
}
openBatchRenameModal() {
const activeFile = this.getActiveFile();
const modal = new ImageBatchRenameModal(
this.app,
activeFile,
(file, name) => __async(this, null, function* () {
yield this.renameFile(file, name, activeFile.path);
}),
() => {
this.modals.splice(this.modals.indexOf(modal), 1);
}
);
this.modals.push(modal);
modal.open();
}
batchRenameAllImages() {
return __async(this, null, function* () {
const activeFile = this.getActiveFile();
const fileCache = this.app.metadataCache.getFileCache(activeFile);
if (!fileCache || !fileCache.embeds)
return;
const extPatternRegex = /jpe?g|png|gif|tiff|webp/i;
for (const embed of fileCache.embeds) {
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, activeFile.path);
if (!file) {
console.warn("file not found", embed.link);
return;
}
const m0 = extPatternRegex.exec(file.extension);
if (!m0)
return;
const { newName, isMeaningful } = this.generateNewName(file, activeFile);
debugLog("generated newName:", newName, isMeaningful);
if (!isMeaningful) {
new import_obsidian2.Notice("Failed to batch rename images: the generated name is not meaningful");
break;
}
yield this.renameFile(file, newName, activeFile.path, false);
}
});
}
// returns a new name for the input file, with extension
generateNewName(file, activeFile) {
let imageNameKey = "";
let firstHeading = "";
let frontmatter;
const fileCache = this.app.metadataCache.getFileCache(activeFile);
if (fileCache) {
debugLog("frontmatter", fileCache.frontmatter);
frontmatter = fileCache.frontmatter;
imageNameKey = (frontmatter == null ? void 0 : frontmatter.imageNameKey) || "";
firstHeading = getFirstHeading(fileCache.headings);
} else {
console.warn("could not get file cache from active file", activeFile.name);
}
const stem = renderTemplate(
this.settings.imageNamePattern,
{
imageNameKey,
fileName: activeFile.basename,
dirName: activeFile.parent.name,
firstHeading
},
frontmatter
);
const meaninglessRegex = new RegExp(`[${this.settings.dupNumberDelimiter}\\s]`, "gm");
return {
stem,
newName: stem + "." + file.extension,
isMeaningful: stem.replace(meaninglessRegex, "") !== ""
};
}
// newName: foo.ext
deduplicateNewName(newName, file) {
return __async(this, null, function* () {
const dir = file.parent.path;
const listed = yield this.app.vault.adapter.list(dir);
debugLog("sibling files", listed);
const newNameExt = path.extension(newName), newNameStem = newName.slice(0, newName.length - newNameExt.length - 1), newNameStemEscaped = escapeRegExp(newNameStem), delimiter = this.settings.dupNumberDelimiter, delimiterEscaped = escapeRegExp(delimiter);
let dupNameRegex;
if (this.settings.dupNumberAtStart) {
dupNameRegex = new RegExp(
`^(?<number>\\d+)${delimiterEscaped}(?<name>${newNameStemEscaped})\\.${newNameExt}$`
);
} else {
dupNameRegex = new RegExp(
`^(?<name>${newNameStemEscaped})${delimiterEscaped}(?<number>\\d+)\\.${newNameExt}$`
);
}
debugLog("dupNameRegex", dupNameRegex);
const dupNameNumbers = [];
let isNewNameExist = false;
for (let sibling of listed.files) {
sibling = path.basename(sibling);
if (sibling == newName) {
isNewNameExist = true;
continue;
}
const m = dupNameRegex.exec(sibling);
if (!m)
continue;
dupNameNumbers.push(parseInt(m.groups.number));
}
if (isNewNameExist || this.settings.dupNumberAlways) {
const newNumber = dupNameNumbers.length > 0 ? Math.max(...dupNameNumbers) + 1 : 1;
if (this.settings.dupNumberAtStart) {
newName = `${newNumber}${delimiter}${newNameStem}.${newNameExt}`;
} else {
newName = `${newNameStem}${delimiter}${newNumber}.${newNameExt}`;
}
}
return {
name: newName,
stem: newName.slice(0, newName.length - newNameExt.length - 1),
extension: newNameExt
};
});
}
getActiveFile() {
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
const file = view == null ? void 0 : view.file;
debugLog("active file", file == null ? void 0 : file.path);
return file;
}
getActiveEditor() {
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
return view == null ? void 0 : view.editor;
}
onunload() {
this.modals.map((modal) => modal.close());
}
testExcludeExtension(file) {
const pattern = this.settings.excludeExtensionPattern;
if (!pattern)
return false;
return new RegExp(pattern).test(file.extension);
}
loadSettings() {
return __async(this, null, function* () {
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
});
}
saveSettings() {
return __async(this, null, function* () {
yield this.saveData(this.settings);
});
}
};
function getFirstHeading(headings) {
if (headings && headings.length > 0) {
for (const heading of headings) {
if (heading.level === 1) {
return heading.heading;
}
}
}
return "";
}
function isPastedImage(file) {
if (file instanceof import_obsidian2.TFile) {
if (file.name.startsWith(PASTED_IMAGE_PREFIX)) {
return true;
}
}
return false;
}
function isMarkdownFile(file) {
if (file instanceof import_obsidian2.TFile) {
if (file.extension === "md") {
return true;
}
}
return false;
}
var ImageRenameModal = class extends import_obsidian2.Modal {
constructor(app, src, stem, renameFunc, onClose) {
super(app);
this.src = src;
this.stem = stem;
this.renameFunc = renameFunc;
this.onCloseExtra = onClose;
}
onOpen() {
this.containerEl.addClass("image-rename-modal");
const { contentEl, titleEl } = this;
titleEl.setText("Rename image");
const imageContainer = contentEl.createDiv({
cls: "image-container"
});
imageContainer.createEl("img", {
attr: {
src: this.app.vault.getResourcePath(this.src)
}
});
let stem = this.stem;
const ext = this.src.extension;
const getNewName = (stem2) => stem2 + "." + ext;
const getNewPath = (stem2) => path.join(this.src.parent.path, getNewName(stem2));
const infoET = createElementTree(contentEl, {
tag: "ul",
cls: "info",
children: [
{
tag: "li",
children: [
{
tag: "span",
text: "Origin path"
},
{
tag: "span",
text: this.src.path
}
]
},
{
tag: "li",
children: [
{
tag: "span",
text: "New path"
},
{
tag: "span",
text: getNewPath(stem)
}
]
}
]
});
const doRename = () => __async(this, null, function* () {
debugLog("doRename", `stem=${stem}`);
this.renameFunc(getNewName(stem));
});
const nameSetting = new import_obsidian2.Setting(contentEl).setName("New name").setDesc("Please input the new name for the image (without extension)").addText((text) => text.setValue(stem).onChange(
(value) => __async(this, null, function* () {
stem = sanitizer.filename(value);
infoET.children[1].children[1].el.innerText = getNewPath(stem);
})
));
const nameInputEl = nameSetting.controlEl.children[0];
nameInputEl.focus();
const nameInputState = lockInputMethodComposition(nameInputEl);
nameInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !nameInputState.lock) {
e.preventDefault();
if (!stem) {
errorEl.innerText = 'Error: "New name" could not be empty';
errorEl.style.display = "block";
return;
}
doRename();
this.close();
}
}));
const errorEl = contentEl.createDiv({
cls: "error",
attr: {
style: "display: none;"
}
});
new import_obsidian2.Setting(contentEl).addButton((button) => {
button.setButtonText("Rename").onClick(() => {
doRename();
this.close();
});
}).addButton((button) => {
button.setButtonText("Cancel").onClick(() => {
this.close();
});
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.onCloseExtra();
}
};
var imageNamePatternDesc = `
The pattern indicates how the new name should be generated.
Available variables:
- {{fileName}}: name of the active file, without ".md" extension.
- {{imageNameKey}}: this variable is read from the markdown file's frontmatter, from the same key "imageNameKey".
- {{DATE:$FORMAT}}: use "$FORMAT" to format the current date, "$FORMAT" must be a Moment.js format string, e.g. {{DATE:YYYY-MM-DD}}.
Here are some examples from pattern to image names (repeat in sequence), variables: fileName = "My note", imageNameKey = "foo":
- {{fileName}}: My note, My note-1, My note-2
- {{imageNameKey}}: foo, foo-1, foo-2
- {{imageNameKey}}-{{DATE:YYYYMMDD}}: foo-20220408, foo-20220408-1, foo-20220408-2
`;
var SettingTab = class extends import_obsidian2.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
new import_obsidian2.Setting(containerEl).setName("Image name pattern").setDesc(imageNamePatternDesc).setClass("long-description-setting-item").addText((text) => text.setPlaceholder("{{imageNameKey}}").setValue(this.plugin.settings.imageNamePattern).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.imageNamePattern = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Duplicate number at start (or end)").setDesc(`If enabled, duplicate number will be added at the start as prefix for the image name, otherwise it will be added at the end as suffix for the image name.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAtStart).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberAtStart = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Duplicate number delimiter").setDesc(`The delimiter to generate the number prefix/suffix for duplicated names. For example, if the value is "-", the suffix will be like "-1", "-2", "-3", and the prefix will be like "1-", "2-", "3-". Only characters that are valid in file names are allowed.`).addText((text) => text.setValue(this.plugin.settings.dupNumberDelimiter).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberDelimiter = sanitizer.delimiter(value);
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Always add duplicate number").setDesc(`If enabled, duplicate number will always be added to the image name. Otherwise, it will only be added when the name is duplicated.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAlways).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberAlways = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Auto rename").setDesc(`By default, the rename modal will always be shown to confirm before renaming, if this option is set, the image will be auto renamed after pasting.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.autoRename).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.autoRename = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Handle all attachments").setDesc(`By default, the plugin only handles images that starts with "Pasted image " in name,
which is the prefix Obsidian uses to create images from pasted content.
If this option is set, the plugin will handle all attachments that are created in the vault.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.handleAllAttachments).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.handleAllAttachments = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Exclude extension pattern").setDesc(`This option is only useful when "Handle all attachments" is enabled.
Write a Regex pattern to exclude certain extensions from being handled. Only the first line will be used.`).setClass("single-line-textarea").addTextArea((text) => text.setPlaceholder("docx?|xlsx?|pptx?|zip|rar").setValue(this.plugin.settings.excludeExtensionPattern).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.excludeExtensionPattern = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Disable rename notice").setDesc(`Turn off this option if you don't want to see the notice when renaming images.
Note that Obsidian may display a notice when a link has changed, this option cannot disable that.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.disableRenameNotice).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.disableRenameNotice = value;
yield this.plugin.saveSettings();
})
));
}
};
@@ -1,10 +0,0 @@
{
"id": "obsidian-paste-image-rename",
"name": "Paste image rename",
"version": "1.6.1",
"minAppVersion": "0.12.0",
"description": "Rename pasted images and all the other attchments added to the vault",
"author": "Reorx",
"authorUrl": "https://github.com/reorx",
"isDesktopOnly": false
}
@@ -1,79 +0,0 @@
/* src/styles.css */
:root {
--shadow-color: 0deg 0% 0%;
--shadow-elevation-medium:
0.5px 0.5px 0.7px hsl(var(--shadow-color) / 0.14),
1.1px 1.1px 1.5px -0.9px hsl(var(--shadow-color) / 0.12),
2.4px 2.5px 3.3px -1.8px hsl(var(--shadow-color) / 0.1),
5.3px 5.6px 7.3px -2.7px hsl(var(--shadow-color) / 0.09),
11px 11.4px 15.1px -3.6px hsl(var(--shadow-color) / 0.07);
}
.image-rename-modal .modal {
width: 65%;
min-width: 600px;
}
.image-rename-modal .modal-content {
padding: 10px 5px;
}
.image-rename-modal .image-container {
display: flex;
justify-content: center;
}
.image-rename-modal .info {
padding: 10px 0;
color: var(--text-muted);
user-select: text;
}
.image-rename-modal .info li > span:nth-of-type(1) {
display: inline-block;
width: 6em;
margin-right: .5em;
}
.image-rename-modal .info li > span:nth-of-type(1):after {
content: ":";
float: right;
}
.image-rename-modal .image-container img {
display: block;
max-height: 300px;
box-shadow: var(--shadow-elevation-medium);
}
.image-rename-modal .setting-item-control input {
min-width: 300px;
}
.image-rename-modal .error {
border: 1px solid rgb(201, 90, 90);
color: rgb(134, 22, 22);
padding: 10px;
}
.image-rename-modal table {
font-size: .9em;
line-height: 1.8;
margin-bottom: 1.5em;
user-select: text;
}
.image-rename-modal table td {
padding-right: 1em;
}
.image-rename-modal table thead td {
font-weight: 700;
}
.image-rename-modal table tbody td .file-path {
font-size: .8em;
color: var(--text-faint);
line-height: 1;
}
.long-description-setting-item {
flex-wrap: wrap;
}
.long-description-setting-item .setting-item-description {
white-space: pre-wrap;
line-height: 1.3em;
}
.long-description-setting-item .setting-item-control {
padding-top: 10px;
}
.long-description-setting-item .setting-item-control input {
min-width: 300px;
width: 50%;
}
-208
View File
@@ -1,208 +0,0 @@
{
"recentFiles": [
{
"basename": "jinja",
"path": "000-inbox/jinja.md"
},
{
"basename": "20260328124189",
"path": "000-Inbox/20260328124189.md"
},
{
"basename": "Qwen3.5(通义千问 3.5)系列的多模态图文大模型(Vision-Language Models",
"path": "resource/ai/大模型安装笔记/Qwen3.5(通义千问 3.5)系列的多模态图文大模型(Vision-Language Models.md"
},
{
"basename": "x-csrf-token",
"path": "work/移动杭研/业务梳理/x-csrf-token.md"
},
{
"basename": "需求-命中率计算",
"path": "000-inbox/需求-命中率计算.md"
},
{
"basename": "Docker 核心知识与实战笔记",
"path": "000-inbox/Docker 核心知识与实战笔记.md"
},
{
"basename": "GNU Screen 命令全解析",
"path": "resource/系统/GNU Screen 命令全解析.md"
},
{
"basename": "结构化输出和工具调用",
"path": "resource/ai/大模型安装笔记/结构化输出和工具调用.md"
},
{
"basename": "如何成为一个 React 工程师呢?",
"path": "resource/前端/如何成为一个 React 工程师呢?.md"
},
{
"basename": "20260327113359",
"path": "000-Inbox/20260327113359.md"
},
{
"basename": "20260327105648",
"path": "000-Inbox/20260327105648.md"
},
{
"basename": "20260327101490",
"path": "000-Inbox/20260327101490.md"
},
{
"basename": "开发笔记",
"path": "work/移动杭研/开发记录/7.19.0/开发笔记.md"
},
{
"basename": "IBS 智能体具体落实技术方案",
"path": "work/移动杭研/AI 项目/IBS 智能体具体落实技术方案.md"
},
{
"basename": "20260327091590",
"path": "000-Inbox/20260327091590.md"
},
{
"basename": "20260327083230",
"path": "000-Inbox/20260327083230.md"
},
{
"basename": "20260326175860",
"path": "000-Inbox/20260326175860.md"
},
{
"basename": "001 大模型启动流程",
"path": "resource/ai/大模型安装笔记/001 大模型启动流程.md"
},
{
"basename": "20260326173620",
"path": "000-Inbox/20260326173620.md"
},
{
"basename": "关于模型思考问题",
"path": "resource/ai/大模型安装笔记/关于模型思考问题.md"
},
{
"basename": "大模型中的参数、权重与数值类型整理",
"path": "resource/ai/大模型安装笔记/大模型中的参数、权重与数值类型整理.md"
},
{
"basename": "LLM 三件套",
"path": "resource/ai/大模型安装笔记/LLM 三件套.md"
},
{
"basename": "000 autodl 机器信息",
"path": "resource/ai/大模型安装笔记/000 autodl 机器信息.md"
},
{
"basename": "函数调用 和 工具调用",
"path": "resource/ai/大模型安装笔记/函数调用 和 工具调用.md"
},
{
"basename": "ubuntu",
"path": "resource/系统/ubuntu.md"
},
{
"basename": "tg-bot",
"path": "000-inbox/tg-bot.md"
},
{
"basename": "20260326141824",
"path": "000-Inbox/20260326141824.md"
},
{
"basename": "20260326140857",
"path": "000-Inbox/20260326140857.md"
},
{
"basename": "20260326093371",
"path": "000-Inbox/20260326093371.md"
},
{
"basename": "核心策略",
"path": "resource/ai/核心策略.md"
},
{
"basename": "002 vllm 安装日志",
"path": "resource/ai/大模型安装笔记/002 vllm 安装日志.md"
},
{
"basename": "netflix 界面切换为简中",
"path": "resource/系统/netflix 界面切换为简中.md"
},
{
"basename": "构建 HUGO 博客的完整指南:从搭建到部署",
"path": "resource/系统/构建 HUGO 博客的完整指南:从搭建到部署.md"
},
{
"basename": "20260325172730",
"path": "000-Inbox/20260325172730.md"
},
{
"basename": "服务器-香港",
"path": "personal/服务器-香港.md"
},
{
"basename": "20260325150017",
"path": "000-Inbox/20260325150017.md"
},
{
"basename": "Prompt 03 COSMIC 子过程",
"path": "resource/ai/prompts/cosmic/Prompt 03 COSMIC 子过程.md"
},
{
"basename": "003 vllm 启动日志",
"path": "resource/ai/大模型安装笔记/003 vllm 启动日志.md"
},
{
"basename": "20260325083535",
"path": "000-Inbox/20260325083535.md"
},
{
"basename": "20260324182248",
"path": "000-Inbox/20260324182248.md"
},
{
"basename": "接口收纳",
"path": "work/移动杭研/业务梳理/接口收纳.md"
},
{
"basename": "开发笔记",
"path": "work/移动杭研/开发记录/7.18.0/开发笔记.md"
},
{
"basename": "0323-邮件发送异常",
"path": "work/移动杭研/问题处理/2026-03/0323-邮件发送异常.md"
},
{
"basename": "TG 备忘录",
"path": "personal/TG 备忘录.md"
},
{
"basename": "CLAUDE.md",
"path": "work/移动杭研/AI 项目/CLAUDE.md.md"
},
{
"basename": "图纸-IBS 智能体-v3-项目架构",
"path": "work/移动杭研/AI 项目/图纸-IBS 智能体-v3-项目架构.md"
},
{
"basename": "图纸-IBS 智能体-v1-流量查询 demo 对象存储",
"path": "work/移动杭研/AI 项目/图纸-IBS 智能体-v1-流量查询 demo 对象存储.md"
},
{
"basename": "图纸-IBS 智能体-v1-流量查询 demo",
"path": "work/移动杭研/AI 项目/图纸-IBS 智能体-v1-流量查询 demo.md"
},
{
"basename": "ibs-ai 项目梳理",
"path": "work/移动杭研/AI 项目/ibs-ai 项目梳理.md"
}
],
"omittedPaths": [
"^attachment/",
"^calendar/"
],
"omittedTags": [],
"updateOn": "file-open",
"omitBookmarks": false,
"maxLength": null
}
File diff suppressed because one or more lines are too long
-16
View File
@@ -1,16 +0,0 @@
{
"id": "recent-files-obsidian",
"name": "Recent Files",
"version": "1.7.6",
"minAppVersion": "0.16.3",
"description": "List files by most recently opened",
"author": "Tony Grosinger",
"authorUrl": "https://grosinger.net",
"isDesktopOnly": false,
"fundingUrl": {
"Github Sponsor": "https://github.com/sponsors/tgrosinger",
"Buy me a Coffee": "https://buymeacoffee.com/tgrosinger",
"Paypal": "https://paypal.me/tgrosinger"
},
"donation": "https://buymeacoffee.com/tgrosinger"
}
-34
View File
@@ -1,34 +0,0 @@
.recent-files-file {
.tree-item-spacer {
flex-grow: 1;
}
}
.recent-files-title {
justify-content: flex-start;
align-items: unset;
}
.recent-files-file-delete {
justify-content: right;
display: none;
}
.recent-files-title:hover .recent-files-file-delete {
display: flex;
cursor: var(--cursor);
}
.recent-files-file-delete:hover {
color: var(--nav-item-color-hover);
}
.recent-files-donation {
width: 70%;
margin: 0 auto;
text-align: center;
}
.recent-files-donate-button {
margin: 10px;
}
-25
View File
@@ -1,25 +0,0 @@
/* === 颜色变量 === */
:root {
--h1-color: #172a4f;
--h2-color: #4a5c7a;
--h3-color: #6b7280;
--bold-color: var(--h1-color);
}
/* === 标题样式 === */
h1 {
color: var(--h1-color);
}
h2 {
color: var(--h2-color);
}
h3 {
color: var(--h3-color);
}
/* === 粗体样式 === */
div.cm-line span.cm-strong,
div p strong {
color: var(--bold-color) !important;
}
+46 -67
View File
@@ -4,11 +4,11 @@
"type": "split", "type": "split",
"children": [ "children": [
{ {
"id": "907e9a7a1202bddd", "id": "f5a820b24c2967c5",
"type": "tabs", "type": "tabs",
"children": [ "children": [
{ {
"id": "b38877d81f5d7556", "id": "5ac4315e91879622",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "empty", "type": "empty",
@@ -29,7 +29,6 @@
{ {
"id": "6836caf2765d7139", "id": "6836caf2765d7139",
"type": "tabs", "type": "tabs",
"dimension": 71.87039764359352,
"children": [ "children": [
{ {
"id": "c8718c0c63702202", "id": "c8718c0c63702202",
@@ -50,7 +49,7 @@
"state": { "state": {
"type": "search", "type": "search",
"state": { "state": {
"query": "openclaw", "query": "path: resource/工具/obsidian/scripts ",
"matchingCase": false, "matchingCase": false,
"explainSearch": true, "explainSearch": true,
"collapseAll": false, "collapseAll": false,
@@ -72,27 +71,10 @@
} }
} }
] ]
},
{
"id": "467278b27553fb8c",
"type": "tabs",
"dimension": 28.12960235640648,
"children": [
{
"id": "c7138b775f1dd80e",
"type": "leaf",
"state": {
"type": "recent-files",
"state": {},
"icon": "clock",
"title": "Recent Files"
}
}
]
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 302.5 "width": 235.5
}, },
"right": { "right": {
"id": "ca733f6d5936ae40", "id": "ca733f6d5936ae40",
@@ -101,7 +83,7 @@
{ {
"id": "1f21045435bfa327", "id": "1f21045435bfa327",
"type": "tabs", "type": "tabs",
"dimension": 57.8883495145631, "dimension": 57.038834951456316,
"children": [ "children": [
{ {
"id": "43a2e8e1d9201229", "id": "43a2e8e1d9201229",
@@ -183,9 +165,7 @@
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "footnotes", "type": "footnotes",
"state": { "state": {},
"file": "000-Inbox/20260305103657.md"
},
"icon": "lucide-file-signature", "icon": "lucide-file-signature",
"title": "脚注" "title": "脚注"
} }
@@ -193,12 +173,12 @@
] ]
}, },
{ {
"id": "ea8a40d57ef90e62", "id": "e2c44b9c5d0747bb",
"type": "tabs", "type": "tabs",
"dimension": 42.1116504854369, "dimension": 42.961165048543684,
"children": [ "children": [
{ {
"id": "499dbd491c6fb256", "id": "736f0891b55bede0",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "calendar", "type": "calendar",
@@ -211,8 +191,7 @@
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 300.5, "width": 297.5
"collapsed": true
}, },
"left-ribbon": { "left-ribbon": {
"hiddenItems": { "hiddenItems": {
@@ -222,48 +201,48 @@
"daily-notes:打开/创建今天的日记": false, "daily-notes:打开/创建今天的日记": false,
"canvas:新建白板": false, "canvas:新建白板": false,
"bases:新建数据库": false, "bases:新建数据库": false,
"templater-obsidian:Templater": true, "obsidian-excalidraw-plugin:New drawing": false,
"obsidian-excalidraw-plugin:New drawing": false "templater-obsidian:Templater": false
} }
}, },
"active": "c8718c0c63702202", "active": "c8718c0c63702202",
"lastOpenFiles": [ "lastOpenFiles": [
"000-inbox/20260328215585.md",
"000-inbox/20260328220243.md",
"000-inbox/20260328213028.md",
"000-Inbox/20260328215585.md",
"000-Inbox/20260328220243.md",
"000-Inbox/20260328213028.md",
"000-inbox/20260328212556.md",
"000-inbox/20260328212865.md",
"000-inbox/20260328182832.md",
"000-inbox/20260328182250.md",
"000-Inbox/20260328212865.md",
"resource/工具/obsidian/x-callback-url.md",
"000-Inbox/20260328212556.md",
"000-Inbox/20260328182832.md",
"000-Inbox/20260328182250.md",
"attachment/templates/移动杭研-问题记录.md",
"attachment/templates/移动杭研-开发笔记.md",
"attachment/templates/日常记录-闪念笔记.md",
"attachment/templates/日常记录-日记模板.md",
"000-inbox/20260328181207.md",
"000-Inbox/20260328181207.md",
"000-inbox/jinja.md", "000-inbox/jinja.md",
"000-inbox/20260328124189.md",
"000-Inbox/20260328124189.md",
"resource/ai/大模型安装笔记/Qwen3.5(通义千问 3.5)系列的多模态图文大模型(Vision-Language Models.md",
"calendar/diary/2026-03-27.md",
"work/移动杭研/业务梳理/x-csrf-token.md",
"calendar/diary/2026-03-26.md",
"000-inbox/需求-命中率计算.md",
"000-inbox/Docker 核心知识与实战笔记.md", "000-inbox/Docker 核心知识与实战笔记.md",
"resource/系统/GNU Screen 命令全解析.md", "000-inbox/需求-命中率计算.md",
"resource/ai/大模型安装笔记/结构化输出和工具调用.md", "000-inbox/20260328145480111111.md",
"resource/前端/如何成为一个 React 工程师呢?.md", "000-inbox/202603281454171.md",
"000-inbox/20260327101490.md", "attachment/images-paste/image-20260325102650978.png",
"000-Inbox/20260327113359.md", "attachment/images-paste/image-20260325224014579.png",
"000-Inbox/20260327151942.md", "attachment/images-paste/image-20260325224132102.png",
"000-inbox/20260327151942.md", "attachment/images-paste/image-20260325224152205.png",
"000-Inbox/20260327105648.md", "attachment/images-paste/image-20260325224201537.png",
"calendar/diary/2026-03-02.md", "attachment/images-paste/image-20260325224141721.png",
"000-Inbox/20260327101490.md", "attachment/images-paste/image-20260325224123001.png",
"calendar/diary/2026-03-23.md", "attachment/images-paste/image-20260325224113880.png",
"calendar/diary/2026-03-20.md", "attachment/images-paste/image-20260325224055087.png",
"calendar/diary/2026-03-21.md", "attachment/images-paste/image-20260325224046176.png",
"calendar/diary/2026-03-25.md",
"calendar/diary/2026-03-24.md",
"calendar/diary/2026-03-19.md",
"calendar/diary/2026-03-18.md",
"attachment/image-20260325224201537.png",
"attachment/image-20260325224152205.png",
"attachment/image-20260325224141721.png",
"attachment/image-20260325224132102.png",
"attachment/image-20260325224123001.png",
"attachment/image-20260325224113880.png",
"attachment/image-20260325224055087.png",
"attachment/image-20260325224046176.png",
"attachment/image-20260325224034041.png",
"attachment/image-20260325224025519.png",
"resource/ai/大模型安装笔记", "resource/ai/大模型安装笔记",
"work/移动杭研/问题处理/2026-03", "work/移动杭研/问题处理/2026-03",
"resource/ai/prompts/cosmic 业务版本", "resource/ai/prompts/cosmic 业务版本",

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

+1 -1
View File
@@ -26,7 +26,7 @@ DeepSeek-V3.2
| `--enable-auto-tool-choice` | 默认关闭(False | 传 `--enable-auto-tool-choice` 开启;或显式传 `--no-enable-auto-tool-choice` 关闭 | 允许模型自己决定是否调用工具 | 不开时,模型通常不会主动发起 tool call;开了后,模型可根据问题自动选择 MCP/tool | | `--enable-auto-tool-choice` | 默认关闭(False | 传 `--enable-auto-tool-choice` 开启;或显式传 `--no-enable-auto-tool-choice` 关闭 | 允许模型自己决定是否调用工具 | 不开时,模型通常不会主动发起 tool call;开了后,模型可根据问题自动选择 MCP/tool |
| `--tool-call-parser` | 默认无(None) | 传一个解析器名字,比如 `openai``llama3_json``hermes``mistral``qwen3_xml` 等 | 指定如何解析模型输出的工具调用格式 | 不传时,即使模型输出了工具调用内容,也可能无法被正确识别;传错了会导致 tool call 解析失败 | | `--tool-call-parser` | 默认无(None) | 传一个解析器名字,比如 `openai``llama3_json``hermes``mistral``qwen3_xml` 等 | 指定如何解析模型输出的工具调用格式 | 不传时,即使模型输出了工具调用内容,也可能无法被正确识别;传错了会导致 tool call 解析失败 |
![](../../attachment/image-20260325141838198.png) ![](../../attachment/images-paste/image-20260325141838198.png)
# 总结 # 总结
@@ -200,7 +200,7 @@ benchmark
} }
``` ```
![](../../../attachment/image-20260325103309652.png) ![](../../../attachment/images-paste/image-20260325103309652.png)
``` ```
{ {
@@ -254,4 +254,4 @@ benchmark
} }
``` ```
![](../../../attachment/image-20260325103231202.png) ![](../../../attachment/images-paste/image-20260325103231202.png)
@@ -19,7 +19,7 @@
4. 然后将请求发出去就可以了。(这里我是把 bash 的 cURL 放到了 Postman 中请求了一下。大家可以任意发挥,只要是能请求通就行啦。) 4. 然后将请求发出去就可以了。(这里我是把 bash 的 cURL 放到了 Postman 中请求了一下。大家可以任意发挥,只要是能请求通就行啦。)
5. 然后你的电脑和手机都会变为简中。 5. 然后你的电脑和手机都会变为简中。
![](../../attachment/image-20260325224152205.png) ![](../../attachment/images-paste/image-20260325224152205.png)
> [!TIP] > [!TIP]
> >
@@ -27,4 +27,4 @@
> >
> 它可能是空白或者 Dansk,不用管,后台配置已经是简中了。 > 它可能是空白或者 Dansk,不用管,后台配置已经是简中了。
![](../../attachment/image-20260325224201537.png) ![](../../attachment/images-paste/image-20260325224201537.png)
@@ -477,7 +477,7 @@ jobs:
对应了设置中的如下配置,我图片存储在了 images 下: 对应了设置中的如下配置,我图片存储在了 images 下:
![](../../attachment/image-20260325223507125.png) ![](../../attachment/images-paste/image-20260325223507125.png)
**Hugo 配置** **Hugo 配置**
@@ -515,19 +515,19 @@ attachments/
我的配置如下: 我的配置如下:
![](../../attachment/image-20260325223528301.png) ![](../../attachment/images-paste/image-20260325223528301.png)
上图重 `Attachment location` 需为 `/`,否则不适配插件会有找不到图片的异常信息。 上图重 `Attachment location` 需为 `/`,否则不适配插件会有找不到图片的异常信息。
![](../../attachment/image-20260325224014579.png) ![](../../attachment/images-paste/image-20260325224014579.png)
上图所需要的配置在 R2 配置界面均可以找到: 上图所需要的配置在 R2 配置界面均可以找到:
![](../../attachment/image-20260325224025519.png) ![](../../attachment/images-paste/image-20260325224025519.png)
存储桶设置中,推荐自定义域,当然前提时你需要在 Cloud flare 进行域名托管。才可以使用子域。 存储桶设置中,推荐自定义域,当然前提时你需要在 Cloud flare 进行域名托管。才可以使用子域。
![](../../attachment/image-20260325224034041.png) ![](../../attachment/images-paste/image-20260325224034041.png)
# 缓存加速 # 缓存加速
@@ -541,21 +541,21 @@ attachments/
将域名托管到 Cf 后,可以新增 Cache Rules。 将域名托管到 Cf 后,可以新增 Cache Rules。
![](../../attachment/image-20260325224046176.png) ![](../../attachment/images-paste/image-20260325224046176.png)
具体规则如下: 具体规则如下:
![](../../attachment/image-20260325224055087.png) ![](../../attachment/images-paste/image-20260325224055087.png)
![](../../attachment/image-20260325224113880.png) ![](../../attachment/images-paste/image-20260325224113880.png)
## 3. SSL/TLS 加密 ## 3. SSL/TLS 加密
![](../../attachment/image-20260325224123001.png) ![](../../attachment/images-paste/image-20260325224123001.png)
![](../../attachment/image-20260325224132102.png) ![](../../attachment/images-paste/image-20260325224132102.png)
![](../../attachment/image-20260325224141721.png) ![](../../attachment/images-paste/image-20260325224141721.png)
CloudFlare 为用户提供的源服务器证书是由 Cloudflare 签名的免费 TLS 证书,该域名证书属于泛域名证书,最长支持 15 年,主要用于源服务器和 Cloudflare 之间的流量加密。但是这个证书属于自签名证书,证书链不完整,缺少根证书。 CloudFlare 为用户提供的源服务器证书是由 Cloudflare 签名的免费 TLS 证书,该域名证书属于泛域名证书,最长支持 15 年,主要用于源服务器和 Cloudflare 之间的流量加密。但是这个证书属于自签名证书,证书链不完整,缺少根证书。