1- import { Plugin , Notice , Modal , App } from "obsidian" ;
1+ import { Plugin , Notice , Modal , App , MarkdownRenderer } from "obsidian" ;
22
33interface Snippet {
44 id : string ;
@@ -240,6 +240,29 @@ class CssSnippetStoreModal extends Modal {
240240
241241 card . createDiv ( { cls : 'snippet-store-button-wrapper' } ) ;
242242
243+ card . addEventListener ( 'click' , async ( event ) => {
244+ // Prevent click events on buttons inside the card from triggering README modal
245+ if ( ( event . target as HTMLElement ) . tagName . toLowerCase ( ) === 'button' ) return ;
246+
247+ const readmeUrl = `https://raw.githubusercontent.com/${ snippet . repo } /refs/heads/main/${ snippet . folder } /README.md` ;
248+ try {
249+ if ( await isOnline ( ) ) {
250+ const response = await fetchWithTimeout ( readmeUrl ) ;
251+ if ( ! response . ok ) {
252+ new Notice ( `Could not fetch README: ${ response . statusText } ` ) ;
253+ return ;
254+ }
255+ const readme = await response . text ( ) ;
256+ new SnippetReadmeModal ( this . app , snippet , readme ) . open ( ) ;
257+ } else {
258+ new Notice ( "No Internet connection..." ) ;
259+ }
260+ } catch ( error ) {
261+ console . error ( error ) ;
262+ new Notice ( `Error fetching README: ${ error . message } ` ) ;
263+ }
264+ } ) ;
265+
243266 // Now update just the button based on snippet state
244267 this . updateSnippetCard ( snippet ) ;
245268 } ) ;
@@ -320,4 +343,67 @@ export async function isOnline(timeout = 3000): Promise<boolean> {
320343 } catch ( e ) {
321344 return false ;
322345 }
346+ }
347+
348+ class SnippetReadmeModal extends Modal {
349+ constructor (
350+ app : App ,
351+ private snippet : Snippet ,
352+ private readmeContent : string
353+ ) {
354+ super ( app ) ;
355+ }
356+
357+ async onOpen ( ) {
358+ const { contentEl } = this ;
359+ contentEl . empty ( ) ;
360+ contentEl . addClass ( "snippet-readme-modal" ) ;
361+ this . modalEl . style . width = "80vw" ;
362+ this . modalEl . style . height = "80vh" ;
363+
364+ // Title
365+ contentEl . createEl ( "h2" , {
366+ text : `${ this . snippet . name } – README` ,
367+ } ) ;
368+
369+ // Rewrite relative image paths to absolute GitHub raw URLs
370+ const adjustedContent = this . rewriteRelativeMediaPaths ( this . readmeContent ) ;
371+
372+ // Markdown container
373+ const markdownContainer = contentEl . createDiv ( ) ;
374+ markdownContainer . style . overflowY = "auto" ;
375+ markdownContainer . style . maxHeight = "65vh" ;
376+ markdownContainer . style . padding = "1rem" ;
377+ markdownContainer . style . backgroundColor = "var(--background-secondary)" ;
378+ markdownContainer . style . borderRadius = "8px" ;
379+
380+ // Render Markdown using Obsidian's renderer
381+ await MarkdownRenderer . renderMarkdown (
382+ adjustedContent ,
383+ markdownContainer ,
384+ "" ,
385+ this
386+ ) ;
387+
388+ markdownContainer . querySelectorAll ( "img" ) . forEach ( ( img ) => {
389+ img . style . maxWidth = "100%" ;
390+ img . style . height = "auto" ;
391+ img . style . display = "block" ;
392+ img . style . margin = "1rem auto" ; // Optional: center images
393+ } ) ;
394+ }
395+
396+ onClose ( ) {
397+ this . contentEl . empty ( ) ;
398+ }
399+
400+ private rewriteRelativeMediaPaths ( content : string ) : string {
401+ const base = `https://raw.githubusercontent.com/${ this . snippet . repo } /refs/heads/main/${ this . snippet . folder } /` ;
402+
403+ // Regex to match image/video markdown with relative path
404+ return content . replace ( / ! \[ ( [ ^ \] ] * ) \] \( ( \. \/ [ ^ ) ] + ) \) / g, ( match , alt , relPath ) => {
405+ const url = base + relPath . replace ( "./" , "" ) ;
406+ return `` ;
407+ } ) ;
408+ }
323409}
0 commit comments