@@ -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" ;
1515import { DEFAULT_SETTINGS , PluginSettings } from "./settings" ;
1616import { 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
1827export 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}
0 commit comments