33 *
44 * Drop-in replacement for mashlib using solid-panes-jss with solid-oidc authentication.
55 * Provides the same interface as mashlib for easy testing and migration.
6+ *
7+ * Supports @view proposal for JSON-LD (self-describing view hint)
8+ * See: https://github.com/w3c/json-ld-syntax/issues/384
69 */
710
811import * as $rdf from 'rdflib'
912import * as panes from 'solid-panes-jss'
1013import * as UI from 'solid-ui-jss'
1114import { authn , solidLogicSingleton , authSession , store } from 'solid-logic-jss'
1215import { versionInfo } from './versionInfo'
13- import { shimStyle } from './styles'
16+ import { shimStyle , injectJssTheme } from './styles'
1417
1518const global : any = window
19+ const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
20+
21+ /**
22+ * Pane module interface for @view
23+ */
24+ interface PaneModule {
25+ name ?: string
26+ render : ( subject : $rdf . NamedNode , context : PaneContext ) => HTMLElement
27+ }
28+
29+ interface PaneContext {
30+ dom : Document
31+ session : { store : $rdf . Store }
32+ }
33+
34+ /**
35+ * Parse JSON-LD data island from page into RDF store
36+ */
37+ function parseJsonLdToStore ( jsonld : any , baseUri : string , targetStore : $rdf . Store ) : $rdf . NamedNode {
38+ const rootId = baseUri + ( jsonld [ '@id' ] || '#thing' )
39+
40+ function addTriples ( node : any , subjectUri : string ) {
41+ const subject = $rdf . sym ( subjectUri )
42+
43+ // Add type
44+ if ( node [ '@type' ] ) {
45+ const typeUri = node [ '@type' ] . replace ( 'schema:' , 'http://schema.org/' )
46+ targetStore . add ( subject , $rdf . sym ( RDF_TYPE ) , $rdf . sym ( typeUri ) )
47+ }
48+
49+ // Add properties
50+ Object . entries ( node ) . forEach ( ( [ key , val ] : [ string , any ] ) => {
51+ if ( key . startsWith ( '@' ) ) return
52+ const pred = $rdf . sym ( key . replace ( 'schema:' , 'http://schema.org/' ) )
53+
54+ if ( Array . isArray ( val ) ) {
55+ val . forEach ( ( item : any , i : number ) => {
56+ if ( typeof item === 'object' && ! item [ '@id' ] ) {
57+ const blankId = `${ subjectUri } _${ key } _${ i } `
58+ targetStore . add ( subject , pred , $rdf . sym ( blankId ) )
59+ addTriples ( item , blankId )
60+ } else if ( typeof item === 'object' && item [ '@id' ] ) {
61+ const uri = item [ '@id' ] . startsWith ( 'http' ) ? item [ '@id' ] : baseUri + item [ '@id' ]
62+ targetStore . add ( subject , pred , $rdf . sym ( uri ) )
63+ } else {
64+ targetStore . add ( subject , pred , item )
65+ }
66+ } )
67+ } else if ( typeof val === 'object' && val [ '@id' ] ) {
68+ const uri = val [ '@id' ] . startsWith ( 'http' ) ? val [ '@id' ] : baseUri + val [ '@id' ]
69+ targetStore . add ( subject , pred , $rdf . sym ( uri ) )
70+ } else if ( typeof val === 'object' ) {
71+ const blankId = `${ subjectUri } _${ key } `
72+ targetStore . add ( subject , pred , $rdf . sym ( blankId ) )
73+ addTriples ( val , blankId )
74+ } else {
75+ targetStore . add ( subject , pred , val )
76+ }
77+ } )
78+ }
79+
80+ addTriples ( jsonld , rootId )
81+ return $rdf . sym ( rootId )
82+ }
83+
84+ /**
85+ * Render JSON-LD using @view
86+ *
87+ * Implements the @view proposal: data specifies its own preferred renderer.
88+ * See: https://github.com/w3c/json-ld-syntax/issues/384
89+ *
90+ * @param options.target - Element to render into (default: creates one after JSON-LD script)
91+ * @param options.subject - Subject ID to render (default: @id from JSON-LD or '#thing')
92+ * @param options.fallbackPane - Pane to use if @view fails or is missing
93+ */
94+ async function renderView ( options : {
95+ target ?: HTMLElement | string
96+ subject ?: string
97+ fallbackPane ?: PaneModule
98+ } = { } ) : Promise < { subject : $rdf . NamedNode ; store : $rdf . Store } | null > {
99+ // Find JSON-LD on page
100+ const jsonLdScript = document . querySelector ( 'script[type="application/ld+json"]' )
101+ if ( ! jsonLdScript ) {
102+ console . warn ( '[solid-shim] No JSON-LD found on page' )
103+ return null
104+ }
105+
106+ let jsonld : any
107+ try {
108+ jsonld = JSON . parse ( jsonLdScript . textContent || '' )
109+ } catch ( e ) {
110+ console . error ( '[solid-shim] Invalid JSON-LD:' , e )
111+ return null
112+ }
113+
114+ // Parse into store
115+ const baseUri = window . location . href . split ( '#' ) [ 0 ]
116+ const subjectId = options . subject || jsonld [ '@id' ] || '#thing'
117+ const subject = parseJsonLdToStore ( jsonld , baseUri , store )
118+
119+ // Override subject if specified
120+ const finalSubject = options . subject
121+ ? $rdf . sym ( baseUri + options . subject )
122+ : subject
123+
124+ // Load pane from @view or fallback
125+ let pane : PaneModule | undefined
126+ if ( jsonld [ '@view' ] ) {
127+ try {
128+ console . log ( `[solid-shim] Loading @view: ${ jsonld [ '@view' ] } ` )
129+ const paneModule = await import ( /* webpackIgnore: true */ jsonld [ '@view' ] )
130+ pane = paneModule . default || paneModule
131+ } catch ( err ) {
132+ console . warn ( `[solid-shim] Failed to load @view "${ jsonld [ '@view' ] } ":` , err )
133+ }
134+ }
135+
136+ if ( ! pane && options . fallbackPane ) {
137+ pane = options . fallbackPane
138+ }
139+
140+ if ( ! pane ) {
141+ console . warn ( '[solid-shim] No @view specified and no fallback pane provided' )
142+ return { subject : finalSubject , store }
143+ }
144+
145+ // Find or create target element
146+ let target : HTMLElement | null = null
147+ if ( typeof options . target === 'string' ) {
148+ target = document . querySelector ( options . target )
149+ } else if ( options . target ) {
150+ target = options . target
151+ }
152+
153+ if ( ! target ) {
154+ target = document . createElement ( 'div' )
155+ target . id = 'solid-shim-view'
156+ jsonLdScript . parentNode ?. insertBefore ( target , jsonLdScript . nextSibling )
157+ }
158+
159+ // Render
160+ const context : PaneContext = {
161+ dom : document ,
162+ session : { store }
163+ }
164+
165+ try {
166+ const rendered = pane . render ( finalSubject , context )
167+ target . appendChild ( rendered )
168+ } catch ( e : any ) {
169+ console . error ( '[solid-shim] Render error:' , e )
170+ target . innerHTML = `<p style="color: #dc2626; padding: 1rem;">Error rendering: ${ e . message } </p>`
171+ }
172+
173+ return { subject : finalSubject , store }
174+ }
175+
176+ // Inject JSS theme styles immediately
177+ injectJssTheme ( )
16178
17179// Expose globals - same interface as mashlib
18180global . $rdf = $rdf
@@ -28,6 +190,12 @@ global.mashlib = {
28190 versionInfo
29191}
30192
193+ // Expose @view renderer globally
194+ global . solidShim = {
195+ renderView,
196+ parseJsonLdToStore
197+ }
198+
31199/**
32200 * Initialize and run the Solid data browser
33201 * @param uri - Optional URI to display initially
@@ -81,3 +249,5 @@ global.dump = dump
81249// Export for ES modules
82250export { versionInfo }
83251export { $rdf , panes , UI , authn , authSession , store , solidLogicSingleton }
252+ export { renderView , parseJsonLdToStore }
253+ export type { PaneModule , PaneContext }
0 commit comments