Skip to content

Commit bcee4c1

Browse files
committed
Add basic live preview support
1 parent f01926a commit bcee4c1

File tree

3 files changed

+193
-22
lines changed

3 files changed

+193
-22
lines changed

main.ts

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,18 @@ import {
1111
TwitterEmbed,
1212
YouTubeEmbed,
1313
} from "./embeds";
14-
import { debounce, Debouncer, MarkdownView, Plugin } from "obsidian";
14+
import { debounce, Debouncer, MarkdownView, Plugin, setIcon } from "obsidian";
1515
import { DEFAULT_SETTINGS, PluginSettings } from "./settings";
1616
import { SimpleEmbedPluginSettingTab } from "./settings-tab";
17+
import { RangeSetBuilder } from "@codemirror/rangeset";
18+
import {
19+
Decoration,
20+
DecorationSet,
21+
EditorView,
22+
ViewPlugin,
23+
ViewUpdate,
24+
WidgetType,
25+
} from "@codemirror/view";
1726

1827
export default class SimpleEmbedsPlugin extends Plugin {
1928
settings: PluginSettings;
@@ -27,7 +36,7 @@ export default class SimpleEmbedsPlugin extends Plugin {
2736
new GitHubGistEmbed(),
2837
new AppleMusicEmbed(),
2938
new ApplePodcastsEmbed(),
30-
new AppleTVEmbed()
39+
new AppleTVEmbed(),
3140
];
3241
processedMarkdown: Debouncer<[]>;
3342
currentTheme: "dark" | "light";
@@ -39,6 +48,9 @@ export default class SimpleEmbedsPlugin extends Plugin {
3948

4049
this.currentTheme = this._getCurrentTheme();
4150

51+
const ext = this.buildAttributesViewPlugin(this);
52+
this.registerEditorExtension(ext);
53+
4254
this.processedMarkdown = debounce(() => {
4355
this.embedSources.forEach((source) => {
4456
source.afterAllEmbeds?.();
@@ -72,6 +84,7 @@ export default class SimpleEmbedsPlugin extends Plugin {
7284
onunload() {
7385
console.log(`Unloading ${this.manifest.name}`);
7486
this.processedMarkdown = null;
87+
// TODO: Clear all widgets
7588
}
7689

7790
async loadSettings() {
@@ -122,7 +135,12 @@ export default class SimpleEmbedsPlugin extends Plugin {
122135
});
123136

124137
if (embedSource && replaceWithEmbed) {
125-
const embed = embedSource.createEmbed(href, container, this.settings, this.currentTheme);
138+
const embed = embedSource.createEmbed(
139+
href,
140+
container,
141+
this.settings,
142+
this.currentTheme,
143+
);
126144
if (fullWidth) {
127145
embed.classList.add("full-width");
128146
}
@@ -145,4 +163,122 @@ export default class SimpleEmbedsPlugin extends Plugin {
145163
parent.replaceChild(container, a);
146164
}
147165
}
166+
167+
get isLivePreviewSupported(): boolean {
168+
return (this.app.vault as any).config?.livePreview;
169+
}
170+
171+
buildAttributesViewPlugin(plugin: SimpleEmbedsPlugin) {
172+
class EmbedWidget extends WidgetType {
173+
constructor(
174+
readonly link: string,
175+
readonly embed: EmbedSource,
176+
readonly settings: Readonly<PluginSettings>,
177+
readonly theme: "light" | "dark",
178+
) {
179+
super();
180+
}
181+
182+
eq(other: EmbedWidget) {
183+
return other.link === this.link;
184+
}
185+
186+
toDOM() {
187+
let el = document.createElement("div");
188+
return this.embed.createEmbed(this.link, el, this.settings, this.theme);
189+
}
190+
191+
ignoreEvent() {
192+
return true;
193+
}
194+
}
195+
196+
const viewPlugin = ViewPlugin.fromClass(
197+
class {
198+
decorations: DecorationSet;
199+
200+
constructor(view: EditorView) {
201+
this.decorations = this.buildDecorations(view);
202+
}
203+
204+
update(update: ViewUpdate) {
205+
if (
206+
update.docChanged || update.viewportChanged || update.selectionSet
207+
) {
208+
this.decorations = this.buildDecorations(update.view);
209+
}
210+
}
211+
212+
destroy() {
213+
}
214+
215+
buildDecorations(view: EditorView) {
216+
let builder = new RangeSetBuilder<Decoration>();
217+
218+
let lines: number[] = [];
219+
if (view.state.doc.length > 0) {
220+
lines = Array.from(
221+
{ length: view.state.doc.lines },
222+
(_, i) => i + 1,
223+
);
224+
}
225+
226+
const currentSelections = [...view.state.selection.ranges];
227+
228+
for (let n of lines) {
229+
const line = view.state.doc.line(n);
230+
const startOfLine = line.from;
231+
const endOfLine = line.to;
232+
233+
let currentLine = false;
234+
235+
currentSelections.forEach((r) => {
236+
if (r.from >= startOfLine && r.to <= endOfLine) {
237+
currentLine = true;
238+
return;
239+
}
240+
});
241+
242+
const mdLink = line.text.match(/\[.*\]\(\S*\)/)?.first().trim();
243+
if (!currentLine && mdLink) {
244+
const start = line.text.indexOf(mdLink) + startOfLine;
245+
const end = start + mdLink.length;
246+
let embedSource = plugin.embedSources.find((source) => {
247+
return plugin.settings[source.enabledKey] &&
248+
source.regex.test(line.text);
249+
});
250+
if (embedSource) {
251+
const link = line.text.match(embedSource.regex).first();
252+
const deco = Decoration.replace({
253+
widget: new EmbedWidget(
254+
link,
255+
embedSource,
256+
plugin.settings,
257+
plugin.currentTheme,
258+
),
259+
inclusive: false,
260+
});
261+
if (plugin.settings.keepLinksInPreview) {
262+
if (plugin.settings.embedPlacement === "above") {
263+
builder.add(start, start, deco);
264+
} else if (plugin.settings.embedPlacement === "below") {
265+
builder.add(end, end, deco);
266+
}
267+
} else {
268+
builder.add(start, end, deco);
269+
}
270+
}
271+
}
272+
}
273+
274+
return builder.finish();
275+
}
276+
},
277+
{
278+
decorations: (v) => v.decorations,
279+
},
280+
);
281+
282+
return viewPlugin;
283+
}
148284
}

package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,24 @@
1212
"author": "",
1313
"license": "MIT",
1414
"devDependencies": {
15+
"@codemirror/rangeset": "^0.19.0",
16+
"@codemirror/state": "^0.19.0",
17+
"@codemirror/view": "^0.19.0",
18+
"@codemirror/commands": "^0.19.0",
19+
"@codemirror/fold": "0.19.0",
20+
"@codemirror/history": "^0.19.0",
21+
"@codemirror/language": "^0.19.0",
22+
"@codemirror/matchbrackets": "^0.19.0",
23+
"@codemirror/panel": "^0.19.0",
24+
"@codemirror/search": "^0.19.0",
25+
"@codemirror/stream-parser": "https://github.com/lishid/stream-parser",
1526
"@rollup/plugin-commonjs": "^18.0.0",
1627
"@rollup/plugin-node-resolve": "^11.2.1",
1728
"@rollup/plugin-typescript": "^8.2.1",
1829
"@types/jest": "^27.0.2",
1930
"@types/node": "^14.14.37",
2031
"jest": "^27.3.1",
21-
"obsidian": "^0.12.0",
32+
"obsidian": "^0.13.0",
2233
"rollup": "^2.32.1",
2334
"ts-jest": "^27.0.7",
2435
"tslib": "^2.2.0",

rollup.config.js

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,54 @@
1-
import typescript from '@rollup/plugin-typescript';
2-
import {nodeResolve} from '@rollup/plugin-node-resolve';
3-
import commonjs from '@rollup/plugin-commonjs';
1+
import typescript from "@rollup/plugin-typescript";
2+
import { nodeResolve } from "@rollup/plugin-node-resolve";
3+
import commonjs from "@rollup/plugin-commonjs";
4+
import builtins from "builtin-modules";
45

5-
const isProd = (process.env.BUILD === 'production');
6+
const isProd = process.env.BUILD === "production";
67

7-
const banner =
8-
`/*
8+
const banner = `/*
99
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
1010
if you want to view the source visit the plugins github repository
1111
*/
1212
`;
1313

1414
export default {
15-
input: 'main.ts',
15+
input: "main.ts",
1616
output: {
17-
dir: '.',
18-
sourcemap: 'inline',
17+
dir: ".",
18+
sourcemap: "inline",
1919
sourcemapExcludeSources: isProd,
20-
format: 'cjs',
21-
exports: 'default',
20+
format: "cjs",
21+
exports: "default",
2222
banner,
2323
},
24-
external: ['obsidian'],
25-
plugins: [
26-
typescript(),
27-
nodeResolve({browser: true}),
28-
commonjs(),
29-
]
30-
};
24+
external: [
25+
"obsidian",
26+
"electron",
27+
"codemirror",
28+
"@codemirror/autocomplete",
29+
"@codemirror/closebrackets",
30+
"@codemirror/collab",
31+
"@codemirror/commands",
32+
"@codemirror/comment",
33+
"@codemirror/fold",
34+
"@codemirror/gutter",
35+
"@codemirror/highlight",
36+
"@codemirror/history",
37+
"@codemirror/language",
38+
"@codemirror/lint",
39+
"@codemirror/matchbrackets",
40+
"@codemirror/panel",
41+
"@codemirror/rangeset",
42+
"@codemirror/rectangular-selection",
43+
"@codemirror/search",
44+
"@codemirror/state",
45+
"@codemirror/stream-parser",
46+
"@codemirror/text",
47+
"@codemirror/tooltip",
48+
"@codemirror/view",
49+
"@lezer/common",
50+
"@lezer/lr",
51+
...builtins,
52+
],
53+
plugins: [typescript(), nodeResolve({ browser: true }), commonjs()],
54+
};

0 commit comments

Comments
 (0)