hiforrest_source-mode-inlin.../main.ts
unknown 7f83ff2c8e 修复 Obsidian 社区审核警告
- 使用 activeDocument 替代 document,兼容弹出窗口
- 移除 this 别名,改用解构赋值获取 app
2026-06-07 22:21:19 +08:00

152 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { App, MarkdownView, Plugin, TFile } from "obsidian";
import {
Decoration,
DecorationSet,
EditorView,
ViewPlugin,
ViewUpdate,
WidgetType,
} from "@codemirror/view";
import { RangeSetBuilder } from "@codemirror/state";
class ImageWidget extends WidgetType {
constructor(
private readonly src: string,
private readonly width?: string,
private readonly errorText?: string
) {
super();
}
toDOM() {
const wrapper = activeDocument.createElement("div");
wrapper.addClass("source-mode-inline-image-wrapper");
if (!this.src) {
wrapper.setText(this.errorText ?? "Image path is empty");
wrapper.addClass("source-mode-inline-image-error");
return wrapper;
}
if (this.width) {
wrapper.style.setProperty("--source-mode-inline-image-width", `${this.width}px`);
wrapper.addClass("source-mode-inline-image-wrapper--custom-width");
}
const img = activeDocument.createElement("img");
img.addClass("source-mode-inline-image");
img.src = this.src;
img.onerror = () => {
wrapper.setText(`Failed to load image: ${this.src}`);
wrapper.addClass("source-mode-inline-image-error");
};
wrapper.appendChild(img);
return wrapper;
}
ignoreEvent() {
return false;
}
}
export default class SourceModeInlineImagesPlugin extends Plugin {
async onload() {
const { app } = this;
this.registerEditorExtension([
ViewPlugin.fromClass(
class {
decorations: DecorationSet;
constructor(view: EditorView) {
this.decorations = buildDecorations(view, app);
}
update(update: ViewUpdate) {
if (
update.docChanged ||
update.viewportChanged ||
update.selectionSet
) {
this.decorations = buildDecorations(update.view, app);
}
}
},
{
decorations: (value) => value.decorations,
}
),
]);
}
}
function isStrictSourceMode(app: App): boolean {
const markdownView = app.workspace.getActiveViewOfType(MarkdownView);
if (!markdownView) return false;
const state = markdownView.getState?.();
return state?.source === true;
}
function buildDecorations(
view: EditorView,
app: App
): DecorationSet {
const builder = new RangeSetBuilder<Decoration>();
// 只在严格源码模式下生效Live Preview 模式跳过(避免与 Obsidian 原生渲染重复)
if (!isStrictSourceMode(app)) return builder.finish();
const activeFile = app.workspace.getActiveFile();
if (!activeFile) return builder.finish();
const sourcePath = activeFile.path;
const wikiImageRegex = /!\[\[([^\]|]+)(?:\|(\d+))?\]\]/g;
for (const { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to);
let match: RegExpExecArray | null;
while ((match = wikiImageRegex.exec(text)) !== null) {
const fullMatch = match[0];
const imagePath = match[1].trim();
const width = match[2];
const start = from + match.index;
const end = start + fullMatch.length;
const file = app.metadataCache.getFirstLinkpathDest(
imagePath,
sourcePath
);
if (!(file instanceof TFile)) {
builder.add(
end,
end,
Decoration.widget({
widget: new ImageWidget("", width, `Image not found: ${imagePath}`),
side: 1,
})
);
continue;
}
const resourcePath = app.vault.getResourcePath(file);
builder.add(
end,
end,
Decoration.widget({
widget: new ImageWidget(resourcePath, width),
side: 1,
})
);
}
}
return builder.finish();
}