Skip to content

Commit dc05384

Browse files
committed
Add support for rendering images from Obsidian wikilinks
1 parent bf0da83 commit dc05384

File tree

3 files changed

+66
-40
lines changed

3 files changed

+66
-40
lines changed

src/frontmatterImageEditorExtension.ts

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,13 @@ import {
1212
} from "@codemirror/view";
1313
import FrontmatterImagePlugin from "./main";
1414
import { Pos, TFile } from "obsidian";
15+
import { getImageSrc, renderFrontmatterImage } from "./utils";
1516

1617
export interface FrontmatterImageStateFieldValue {
1718
readonly activeFile: TFile;
1819
decorationSet?: DecorationSet;
1920
}
2021

21-
export const renderFrontmatterImage = (src: string): HTMLElement => {
22-
const img = document.createElement("img");
23-
img.src = src;
24-
img.classList.add("frontmatter-image");
25-
return img;
26-
}
27-
2822
export const frontmatterImageEditorExtension = (
2923
plugin: FrontmatterImagePlugin,
3024
): StateField<FrontmatterImageStateFieldValue | undefined> => {
@@ -35,9 +29,9 @@ export const frontmatterImageEditorExtension = (
3529

3630
const buildDecorationSet = (
3731
frontmatterPosition: Pos,
38-
currentImageValue?: string,
32+
resolvedImageSrc?: string,
3933
) => {
40-
if (!currentImageValue) return Decoration.none;
34+
if (!resolvedImageSrc) return Decoration.none;
4135

4236
const builder = new RangeSetBuilder<Decoration>();
4337
builder.add(
@@ -46,7 +40,7 @@ export const frontmatterImageEditorExtension = (
4640
Decoration.widget({
4741
widget: new (class extends WidgetType {
4842
toDOM(view: EditorView): HTMLElement {
49-
return renderFrontmatterImage(currentImageValue);
43+
return renderFrontmatterImage(resolvedImageSrc);
5044
}
5145
})(),
5246
}),
@@ -69,29 +63,27 @@ export const frontmatterImageEditorExtension = (
6963
oldValue: FrontmatterImageStateFieldValue | undefined,
7064
transaction: Transaction,
7165
): FrontmatterImageStateFieldValue | undefined => {
72-
if (!oldValue) return;
66+
if (!oldValue) return;
7367

7468
if (!isLivePreview()) {
75-
return { ...oldValue, decorationSet: undefined };
69+
return { ...oldValue, decorationSet: undefined };
7670
}
7771

7872
const cachedMetadata = plugin.app.metadataCache.getFileCache(oldValue.activeFile);
79-
const frontmatterCache = cachedMetadata?.frontmatter;
8073
const frontmatterPosition = cachedMetadata?.frontmatterPosition;
81-
if (!frontmatterCache || !frontmatterPosition) {
82-
return { ...oldValue, decorationSet: undefined };
74+
if (!frontmatterPosition) {
75+
return { ...oldValue, decorationSet: undefined };
8376
}
8477

85-
const currentImageKey = plugin.settings.imageKeys.find(key => frontmatterCache[key]);
86-
const currentImageValue = currentImageKey && frontmatterCache[currentImageKey];
78+
const imageSrc = getImageSrc(oldValue.activeFile.path, plugin);
8779

88-
return {
89-
...oldValue,
90-
decorationSet: buildDecorationSet(
91-
frontmatterPosition,
92-
currentImageValue,
93-
)
94-
};
80+
return {
81+
...oldValue,
82+
decorationSet: buildDecorationSet(
83+
frontmatterPosition,
84+
imageSrc,
85+
)
86+
};
9587
},
9688
provide: (
9789
field: StateField<FrontmatterImageStateFieldValue | undefined>,

src/main.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {
22
App,
33
MarkdownPostProcessorContext,
4+
MarkdownRenderer,
45
Plugin,
56
PluginSettingTab,
6-
Setting,
7+
Setting
78
} from "obsidian";
8-
9-
import { frontmatterImageEditorExtension, renderFrontmatterImage } from "src/frontmatterImageEditorExtension";
9+
import { frontmatterImageEditorExtension, } from "src/frontmatterImageEditorExtension";
10+
import { getImageSrc, renderFrontmatterImage } from "./utils";
1011

1112
interface FrontmatterImagePluginSettings {
1213
imageKeys: string[];
@@ -24,24 +25,19 @@ export default class FrontmatterImagePlugin extends Plugin {
2425

2526
this.addSettingTab(new FrontmatterImageSettingTab(this.app, this));
2627

27-
// Register editor extension for editor view (live preview)
28+
// Register editor extension for editor view (live preview)
2829
this.registerEditorExtension(frontmatterImageEditorExtension(this));
2930

3031
// Register markdown post-processor for reading view
3132
this.registerMarkdownPostProcessor(
3233
(element: HTMLElement, context: MarkdownPostProcessorContext) => {
33-
if (!element.hasClass("mod-frontmatter")) return;
34-
35-
const fileCache = this.app.metadataCache.getCache(context.sourcePath);
36-
const frontmatter = fileCache?.frontmatter;
37-
if (!frontmatter) return;
34+
if (!element.hasClass("mod-frontmatter")) return;
3835

39-
const currentImageKey = this.settings.imageKeys.find(key => frontmatter[key]);
40-
const currentImageValue = currentImageKey && frontmatter[currentImageKey];
41-
if (!currentImageValue) return;
36+
const imageSrc = getImageSrc(context.sourcePath, this);
37+
if (!imageSrc) return;
4238

4339
const div = document.createElement("div");
44-
const img = renderFrontmatterImage(currentImageValue);
40+
const img = renderFrontmatterImage(imageSrc);
4541
div.appendChild(img);
4642
const br = document.createElement("br");
4743
div.appendChild(br);
@@ -50,7 +46,7 @@ export default class FrontmatterImagePlugin extends Plugin {
5046
);
5147
}
5248

53-
onunload() {}
49+
onunload() { }
5450

5551
async loadSettings() {
5652
this.settings = Object.assign(
@@ -81,14 +77,14 @@ class FrontmatterImageSettingTab extends PluginSettingTab {
8177
new Setting(containerEl)
8278
.setName("YAML keys")
8379
.setDesc(
84-
"Frontmatter keys that contain image src; one per line. Only the first populated key's image will be rendered.",
80+
"Frontmatter keys that contain image source; one per line. Only the first populated key's image will be rendered.",
8581
)
8682
.addTextArea((text) =>
8783
text
8884
.setPlaceholder("Enter your keys")
8985
.setValue(this.plugin.settings.imageKeys.join("\n"))
9086
.onChange(async (value) => {
91-
this.plugin.settings.imageKeys = value.split("\n").map(key => key.trim()).filter(key => key.length > 0);
87+
this.plugin.settings.imageKeys = value.split("\n").map(key => key.trim()).filter(key => key.length > 0);
9288
await this.plugin.saveSettings();
9389
}),
9490
);

src/utils.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import FrontmatterImagePlugin from "./main";
2+
3+
export const getImageSrc = (activeFilePath: string, plugin: FrontmatterImagePlugin): string | undefined => {
4+
const fileCache = plugin.app.metadataCache.getCache(activeFilePath);
5+
const frontmatter = fileCache?.frontmatter;
6+
if (!frontmatter) return;
7+
8+
let rawValue = plugin.settings.imageKeys
9+
.map(key => frontmatter[key])
10+
.find(value => !!value);
11+
12+
// Extract image from wikilink format: [[img.jpg|...]] or [[img.jpg]]
13+
const wikilinkMatch = rawValue.match(/^\[\[([^|\]]+)(?:\|[^\]]+)?\]\]$/);
14+
if (wikilinkMatch) {
15+
rawValue = wikilinkMatch[1];
16+
}
17+
18+
// Check if it's already a full URL or path
19+
if (rawValue.startsWith('http') || rawValue.startsWith('/') || rawValue.startsWith('data:')) {
20+
return rawValue;
21+
}
22+
23+
// Try to resolve as an Obsidian file
24+
const file = plugin.app.metadataCache.getFirstLinkpathDest(rawValue, activeFilePath);
25+
if (file) {
26+
return plugin.app.vault.getResourcePath(file);
27+
}
28+
29+
// If we can't resolve it, return the original value
30+
return rawValue;
31+
};
32+
33+
export const renderFrontmatterImage = (src: string): HTMLElement => {
34+
const img = document.createElement("img");
35+
img.src = src;
36+
img.classList.add("frontmatter-image");
37+
return img;
38+
}

0 commit comments

Comments
 (0)