worldwideweb 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,997 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>WorldWideWeb - jsonos</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: #1a1a2e;
17
+ color: #eee;
18
+ height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+
23
+ /* Navigation Bar */
24
+ #navbar {
25
+ display: flex;
26
+ align-items: center;
27
+ gap: 8px;
28
+ padding: 8px 12px;
29
+ background: #16213e;
30
+ border-bottom: 1px solid #0f3460;
31
+ }
32
+
33
+ #navbar button {
34
+ background: #0f3460;
35
+ border: none;
36
+ color: #eee;
37
+ padding: 8px 12px;
38
+ border-radius: 4px;
39
+ cursor: pointer;
40
+ font-size: 14px;
41
+ }
42
+
43
+ #navbar button:hover {
44
+ background: #1a4a7a;
45
+ }
46
+
47
+ #navbar button:disabled {
48
+ opacity: 0.5;
49
+ cursor: not-allowed;
50
+ }
51
+
52
+ #url-bar {
53
+ flex: 1;
54
+ padding: 8px 12px;
55
+ border: 1px solid #0f3460;
56
+ border-radius: 4px;
57
+ background: #0a0a1a;
58
+ color: #eee;
59
+ font-size: 14px;
60
+ }
61
+
62
+ #url-bar:focus {
63
+ outline: none;
64
+ border-color: #e94560;
65
+ }
66
+
67
+ /* Auth Status */
68
+ #auth-status {
69
+ padding: 4px 12px;
70
+ border-radius: 4px;
71
+ font-size: 12px;
72
+ cursor: pointer;
73
+ }
74
+
75
+ #auth-status.logged-out {
76
+ background: #e94560;
77
+ }
78
+
79
+ #auth-status.logged-in {
80
+ background: #4ecca3;
81
+ color: #1a1a2e;
82
+ }
83
+
84
+ /* Main Content */
85
+ #content {
86
+ flex: 1;
87
+ overflow: auto;
88
+ padding: 20px;
89
+ }
90
+
91
+ /* Loading State */
92
+ #loading {
93
+ display: none;
94
+ text-align: center;
95
+ padding: 40px;
96
+ color: #888;
97
+ }
98
+
99
+ #loading.active {
100
+ display: block;
101
+ }
102
+
103
+ /* JSON Viewer */
104
+ #viewer {
105
+ background: #0a0a1a;
106
+ border-radius: 8px;
107
+ padding: 20px;
108
+ min-height: 200px;
109
+ }
110
+
111
+ /* Linked Object Styles */
112
+ .lion-object {
113
+ border: 1px solid #0f3460;
114
+ border-radius: 8px;
115
+ padding: 16px;
116
+ margin-bottom: 16px;
117
+ background: #16213e;
118
+ }
119
+
120
+ .lion-object .lion-id {
121
+ color: #e94560;
122
+ font-size: 12px;
123
+ margin-bottom: 8px;
124
+ word-break: break-all;
125
+ }
126
+
127
+ .lion-object .lion-type {
128
+ color: #4ecca3;
129
+ font-size: 14px;
130
+ font-weight: bold;
131
+ margin-bottom: 12px;
132
+ }
133
+
134
+ .lion-property {
135
+ display: flex;
136
+ margin-bottom: 8px;
137
+ padding: 4px 0;
138
+ border-bottom: 1px solid #0f3460;
139
+ }
140
+
141
+ .lion-property:last-child {
142
+ border-bottom: none;
143
+ }
144
+
145
+ .lion-key {
146
+ color: #7b8cde;
147
+ min-width: 150px;
148
+ font-weight: 500;
149
+ }
150
+
151
+ .lion-value {
152
+ color: #eee;
153
+ flex: 1;
154
+ }
155
+
156
+ .lion-link {
157
+ color: #e94560;
158
+ text-decoration: none;
159
+ cursor: pointer;
160
+ }
161
+
162
+ .lion-link:hover {
163
+ text-decoration: underline;
164
+ }
165
+
166
+ /* Edit Mode */
167
+ .lion-value[contenteditable="true"] {
168
+ background: #0a0a1a;
169
+ padding: 4px 8px;
170
+ border-radius: 4px;
171
+ outline: 1px solid #e94560;
172
+ }
173
+
174
+ /* View Tabs */
175
+ #view-tabs {
176
+ display: flex;
177
+ gap: 8px;
178
+ margin-bottom: 16px;
179
+ }
180
+
181
+ #view-tabs button {
182
+ background: #0f3460;
183
+ border: none;
184
+ color: #eee;
185
+ padding: 6px 16px;
186
+ border-radius: 4px;
187
+ cursor: pointer;
188
+ }
189
+
190
+ #view-tabs button.active {
191
+ background: #e94560;
192
+ }
193
+
194
+ /* Raw JSON View */
195
+ #raw-json {
196
+ display: none;
197
+ background: #0a0a1a;
198
+ padding: 16px;
199
+ border-radius: 8px;
200
+ font-family: 'Monaco', 'Menlo', monospace;
201
+ font-size: 13px;
202
+ white-space: pre-wrap;
203
+ word-break: break-all;
204
+ color: #4ecca3;
205
+ }
206
+
207
+ /* Status Bar */
208
+ #status-bar {
209
+ padding: 4px 12px;
210
+ background: #0f3460;
211
+ font-size: 12px;
212
+ color: #888;
213
+ display: flex;
214
+ justify-content: space-between;
215
+ }
216
+
217
+ /* Custom View Container */
218
+ #custom-view {
219
+ display: none;
220
+ }
221
+
222
+ /* Login Modal */
223
+ #login-modal {
224
+ display: none;
225
+ position: fixed;
226
+ top: 0;
227
+ left: 0;
228
+ width: 100%;
229
+ height: 100%;
230
+ background: rgba(0, 0, 0, 0.8);
231
+ z-index: 1000;
232
+ justify-content: center;
233
+ align-items: center;
234
+ }
235
+
236
+ #login-modal.active {
237
+ display: flex;
238
+ }
239
+
240
+ #login-modal .modal-content {
241
+ background: #16213e;
242
+ padding: 32px;
243
+ border-radius: 12px;
244
+ width: 400px;
245
+ max-width: 90%;
246
+ }
247
+
248
+ #login-modal h2 {
249
+ margin-bottom: 20px;
250
+ color: #4ecca3;
251
+ }
252
+
253
+ #login-modal label {
254
+ display: block;
255
+ margin-bottom: 8px;
256
+ color: #888;
257
+ }
258
+
259
+ #login-modal input {
260
+ width: 100%;
261
+ padding: 12px;
262
+ border: 1px solid #0f3460;
263
+ border-radius: 4px;
264
+ background: #0a0a1a;
265
+ color: #eee;
266
+ font-size: 14px;
267
+ margin-bottom: 16px;
268
+ }
269
+
270
+ #login-modal .providers {
271
+ display: flex;
272
+ flex-wrap: wrap;
273
+ gap: 8px;
274
+ margin-bottom: 20px;
275
+ }
276
+
277
+ #login-modal .provider-btn {
278
+ padding: 8px 16px;
279
+ background: #0f3460;
280
+ border: none;
281
+ color: #eee;
282
+ border-radius: 4px;
283
+ cursor: pointer;
284
+ font-size: 12px;
285
+ }
286
+
287
+ #login-modal .provider-btn:hover {
288
+ background: #1a4a7a;
289
+ }
290
+
291
+ #login-modal .modal-actions {
292
+ display: flex;
293
+ gap: 12px;
294
+ justify-content: flex-end;
295
+ }
296
+
297
+ #login-modal .modal-actions button {
298
+ padding: 10px 24px;
299
+ border: none;
300
+ border-radius: 4px;
301
+ cursor: pointer;
302
+ font-size: 14px;
303
+ }
304
+
305
+ #login-modal .btn-cancel {
306
+ background: #333;
307
+ color: #eee;
308
+ }
309
+
310
+ #login-modal .btn-login {
311
+ background: #4ecca3;
312
+ color: #1a1a2e;
313
+ }
314
+
315
+ /* Welcome Screen */
316
+ #welcome-screen {
317
+ text-align: center;
318
+ padding: 60px 20px;
319
+ }
320
+
321
+ #welcome-screen h1 {
322
+ font-size: 48px;
323
+ margin-bottom: 16px;
324
+ background: linear-gradient(135deg, #e94560, #4ecca3);
325
+ -webkit-background-clip: text;
326
+ -webkit-text-fill-color: transparent;
327
+ background-clip: text;
328
+ }
329
+
330
+ #welcome-screen p {
331
+ color: #888;
332
+ margin-bottom: 32px;
333
+ font-size: 18px;
334
+ }
335
+
336
+ #welcome-screen .quick-links {
337
+ display: flex;
338
+ flex-wrap: wrap;
339
+ gap: 12px;
340
+ justify-content: center;
341
+ margin-top: 32px;
342
+ }
343
+
344
+ #welcome-screen .quick-link {
345
+ padding: 12px 24px;
346
+ background: #0f3460;
347
+ border: none;
348
+ color: #eee;
349
+ border-radius: 8px;
350
+ cursor: pointer;
351
+ font-size: 14px;
352
+ }
353
+
354
+ #welcome-screen .quick-link:hover {
355
+ background: #1a4a7a;
356
+ }
357
+ </style>
358
+ </head>
359
+ <body>
360
+ <!-- Navigation Bar -->
361
+ <nav id="navbar">
362
+ <button id="btn-back" title="Back">←</button>
363
+ <button id="btn-forward" title="Forward">→</button>
364
+ <button id="btn-reload" title="Reload">↻</button>
365
+ <input type="text" id="url-bar" placeholder="Enter URL or @id..." />
366
+ <button id="btn-go">Go</button>
367
+ <button id="btn-edit" title="Toggle Edit Mode">✎</button>
368
+ <span id="auth-status" class="logged-out">Login</span>
369
+ </nav>
370
+
371
+ <!-- Main Content -->
372
+ <main id="content">
373
+ <div id="loading">Loading...</div>
374
+
375
+ <div id="view-tabs">
376
+ <button class="active" data-view="rendered">Rendered</button>
377
+ <button data-view="raw">Raw JSON</button>
378
+ <button data-view="custom">Custom View</button>
379
+ </div>
380
+
381
+ <div id="viewer"></div>
382
+ <pre id="raw-json"></pre>
383
+ <div id="custom-view"></div>
384
+ </main>
385
+
386
+ <!-- Status Bar -->
387
+ <footer id="status-bar">
388
+ <span id="status-text">Ready</span>
389
+ <span id="status-size"></span>
390
+ </footer>
391
+
392
+ <!-- Login Modal -->
393
+ <div id="login-modal">
394
+ <div class="modal-content">
395
+ <h2>Login with Solid</h2>
396
+ <label>Identity Provider (IdP)</label>
397
+ <input type="text" id="idp-input" placeholder="https://solidcommunity.net" value="https://solidcommunity.net" />
398
+
399
+ <label>Quick Providers</label>
400
+ <div class="providers">
401
+ <button class="provider-btn" data-idp="https://solidcommunity.net">solidcommunity.net</button>
402
+ <button class="provider-btn" data-idp="https://login.inrupt.com">inrupt.com</button>
403
+ <button class="provider-btn" data-idp="https://solidweb.org">solidweb.org</button>
404
+ <button class="provider-btn" data-idp="https://teamid.live">teamid.live</button>
405
+ </div>
406
+
407
+ <div class="modal-actions">
408
+ <button class="btn-cancel" id="btn-cancel-login">Cancel</button>
409
+ <button class="btn-login" id="btn-do-login">Login</button>
410
+ </div>
411
+ </div>
412
+ </div>
413
+
414
+ <!-- LION Library (Linked Objects) -->
415
+ <script>
416
+ // Minimal LION implementation (~2KB)
417
+ const LION = {
418
+ cache: new Map(),
419
+
420
+ async fetch(url, options = {}) {
421
+ const headers = {
422
+ 'Accept': 'application/ld+json, application/json, text/turtle',
423
+ ...options.headers
424
+ };
425
+
426
+ try {
427
+ const response = await fetch(url, { ...options, headers });
428
+ const contentType = response.headers.get('content-type') || '';
429
+
430
+ let data;
431
+ if (contentType.includes('json')) {
432
+ data = await response.json();
433
+ } else if (contentType.includes('turtle')) {
434
+ // Basic turtle to JSON-LD conversion for simple cases
435
+ const text = await response.text();
436
+ data = this.turtleToJsonLd(text, url);
437
+ } else {
438
+ data = await response.json();
439
+ }
440
+
441
+ // Normalize to always have @id
442
+ if (!data['@id']) {
443
+ data['@id'] = url;
444
+ }
445
+
446
+ this.cache.set(url, data);
447
+ return data;
448
+ } catch (error) {
449
+ console.error('LION fetch error:', error);
450
+ throw error;
451
+ }
452
+ },
453
+
454
+ // Very basic turtle parser for common patterns
455
+ turtleToJsonLd(turtle, baseUrl) {
456
+ const obj = { '@id': baseUrl };
457
+ const lines = turtle.split('\n');
458
+
459
+ for (const line of lines) {
460
+ const trimmed = line.trim();
461
+ if (trimmed.startsWith('@prefix') || trimmed.startsWith('#') || !trimmed) {
462
+ continue;
463
+ }
464
+ // Basic triple pattern: <subject> <predicate> <object> .
465
+ const match = trimmed.match(/<([^>]+)>\s+<([^>]+)>\s+(?:<([^>]+)>|"([^"]*)")/);
466
+ if (match) {
467
+ const predicate = match[2].split(/[#\/]/).pop();
468
+ const value = match[3] || match[4];
469
+ obj[predicate] = value;
470
+ }
471
+ }
472
+ return obj;
473
+ },
474
+
475
+ async create(url, data) {
476
+ const response = await fetch(url, {
477
+ method: 'PUT',
478
+ headers: {
479
+ 'Content-Type': 'application/ld+json'
480
+ },
481
+ body: JSON.stringify({ '@id': url, ...data })
482
+ });
483
+ return response.ok;
484
+ },
485
+
486
+ async update(url, data) {
487
+ const existing = this.cache.get(url) || {};
488
+ const merged = { ...existing, ...data, '@id': url };
489
+ return this.create(url, merged);
490
+ },
491
+
492
+ async deleteObject(url) {
493
+ const response = await fetch(url, { method: 'DELETE' });
494
+ this.cache.delete(url);
495
+ return response.ok;
496
+ },
497
+
498
+ isUrl(value) {
499
+ if (typeof value !== 'string') return false;
500
+ return value.startsWith('http://') || value.startsWith('https://');
501
+ },
502
+
503
+ getType(data) {
504
+ return data['@type'] || data.type || 'Unknown';
505
+ },
506
+
507
+ getView(data) {
508
+ return data['@view'] || data.view;
509
+ }
510
+ };
511
+
512
+ window.LION = LION;
513
+ </script>
514
+
515
+ <!-- jsonos Runtime -->
516
+ <script>
517
+ const jsonos = {
518
+ panes: new Map(),
519
+ currentData: null,
520
+ editMode: false,
521
+ history: [],
522
+ historyIndex: -1,
523
+
524
+ // Register a pane/view for a type
525
+ registerPane(type, renderFn) {
526
+ this.panes.set(type, renderFn);
527
+ },
528
+
529
+ // Render data using appropriate pane
530
+ async render(data, container) {
531
+ this.currentData = data;
532
+ const type = LION.getType(data);
533
+ const viewUrl = LION.getView(data);
534
+
535
+ // Try custom @view first
536
+ if (viewUrl) {
537
+ try {
538
+ await this.loadCustomView(viewUrl, data, container);
539
+ return;
540
+ } catch (e) {
541
+ console.warn('Custom view failed, using default:', e);
542
+ }
543
+ }
544
+
545
+ // Try registered pane
546
+ if (this.panes.has(type)) {
547
+ const html = this.panes.get(type)(data);
548
+ container.innerHTML = html;
549
+ this.bindLinks(container);
550
+ return;
551
+ }
552
+
553
+ // Default: render as linked object tree
554
+ this.renderDefault(data, container);
555
+ },
556
+
557
+ renderDefault(data, container) {
558
+ const html = this.objectToHtml(data);
559
+ container.innerHTML = html;
560
+ this.bindLinks(container);
561
+ },
562
+
563
+ objectToHtml(obj, depth = 0) {
564
+ if (depth > 5) return '<span class="lion-value">...</span>';
565
+
566
+ let html = '<div class="lion-object">';
567
+
568
+ // @id
569
+ if (obj['@id']) {
570
+ html += `<div class="lion-id">@id: <a class="lion-link" href="${obj['@id']}">${obj['@id']}</a></div>`;
571
+ }
572
+
573
+ // @type
574
+ if (obj['@type']) {
575
+ const type = Array.isArray(obj['@type']) ? obj['@type'].join(', ') : obj['@type'];
576
+ html += `<div class="lion-type">${type}</div>`;
577
+ }
578
+
579
+ // Properties
580
+ for (const [key, value] of Object.entries(obj)) {
581
+ if (key.startsWith('@')) continue;
582
+
583
+ html += '<div class="lion-property">';
584
+ html += `<span class="lion-key">${this.formatKey(key)}</span>`;
585
+ html += `<span class="lion-value" data-key="${key}">${this.valueToHtml(value, depth)}</span>`;
586
+ html += '</div>';
587
+ }
588
+
589
+ html += '</div>';
590
+ return html;
591
+ },
592
+
593
+ valueToHtml(value, depth) {
594
+ if (value === null || value === undefined) {
595
+ return '<em>null</em>';
596
+ }
597
+
598
+ if (Array.isArray(value)) {
599
+ return value.map(v => this.valueToHtml(v, depth)).join('<br>');
600
+ }
601
+
602
+ if (typeof value === 'object') {
603
+ return this.objectToHtml(value, depth + 1);
604
+ }
605
+
606
+ if (LION.isUrl(value)) {
607
+ return `<a class="lion-link" href="${value}">${value}</a>`;
608
+ }
609
+
610
+ return String(value);
611
+ },
612
+
613
+ formatKey(key) {
614
+ // Remove namespace prefixes for display
615
+ const parts = key.split(/[:#\/]/);
616
+ return parts[parts.length - 1];
617
+ },
618
+
619
+ bindLinks(container) {
620
+ container.querySelectorAll('.lion-link').forEach(link => {
621
+ link.addEventListener('click', (e) => {
622
+ e.preventDefault();
623
+ const url = link.getAttribute('href');
624
+ this.navigate(url);
625
+ });
626
+ });
627
+ },
628
+
629
+ async loadCustomView(viewUrl, data, container) {
630
+ const customViewEl = document.getElementById('custom-view');
631
+ const module = await import(viewUrl);
632
+ if (module.render) {
633
+ const result = module.render(data);
634
+ if (typeof result === 'string') {
635
+ customViewEl.innerHTML = result;
636
+ } else {
637
+ customViewEl.innerHTML = '';
638
+ customViewEl.appendChild(result);
639
+ }
640
+ // Switch to custom view tab
641
+ document.querySelector('[data-view="custom"]').click();
642
+ }
643
+ },
644
+
645
+ async navigate(url) {
646
+ if (!url) return;
647
+
648
+ // Update history
649
+ if (this.historyIndex < this.history.length - 1) {
650
+ this.history = this.history.slice(0, this.historyIndex + 1);
651
+ }
652
+ this.history.push(url);
653
+ this.historyIndex = this.history.length - 1;
654
+
655
+ // Update UI
656
+ document.getElementById('url-bar').value = url;
657
+ document.getElementById('loading').classList.add('active');
658
+ document.getElementById('viewer').innerHTML = '';
659
+ document.getElementById('status-text').textContent = 'Loading...';
660
+
661
+ try {
662
+ const data = await LION.fetch(url);
663
+
664
+ // Update raw view
665
+ document.getElementById('raw-json').textContent = JSON.stringify(data, null, 2);
666
+
667
+ // Render
668
+ await this.render(data, document.getElementById('viewer'));
669
+
670
+ document.getElementById('status-text').textContent = `Loaded: ${url}`;
671
+ document.getElementById('status-size').textContent =
672
+ `${JSON.stringify(data).length} bytes`;
673
+ } catch (error) {
674
+ document.getElementById('viewer').innerHTML =
675
+ `<div class="lion-object"><div class="lion-type">Error</div>
676
+ <div class="lion-property"><span class="lion-key">message</span>
677
+ <span class="lion-value">${error.message}</span></div></div>`;
678
+ document.getElementById('status-text').textContent = `Error: ${error.message}`;
679
+ } finally {
680
+ document.getElementById('loading').classList.remove('active');
681
+ }
682
+
683
+ this.updateNavButtons();
684
+ },
685
+
686
+ goBack() {
687
+ if (this.historyIndex > 0) {
688
+ this.historyIndex--;
689
+ const url = this.history[this.historyIndex];
690
+ document.getElementById('url-bar').value = url;
691
+ this.navigateWithoutHistory(url);
692
+ }
693
+ },
694
+
695
+ goForward() {
696
+ if (this.historyIndex < this.history.length - 1) {
697
+ this.historyIndex++;
698
+ const url = this.history[this.historyIndex];
699
+ document.getElementById('url-bar').value = url;
700
+ this.navigateWithoutHistory(url);
701
+ }
702
+ },
703
+
704
+ async navigateWithoutHistory(url) {
705
+ document.getElementById('loading').classList.add('active');
706
+ try {
707
+ const data = await LION.fetch(url);
708
+ document.getElementById('raw-json').textContent = JSON.stringify(data, null, 2);
709
+ await this.render(data, document.getElementById('viewer'));
710
+ document.getElementById('status-text').textContent = `Loaded: ${url}`;
711
+ } catch (error) {
712
+ document.getElementById('viewer').innerHTML =
713
+ `<div class="lion-object"><div class="lion-type">Error</div>
714
+ <div class="lion-property"><span class="lion-key">message</span>
715
+ <span class="lion-value">${error.message}</span></div></div>`;
716
+ } finally {
717
+ document.getElementById('loading').classList.remove('active');
718
+ }
719
+ this.updateNavButtons();
720
+ },
721
+
722
+ updateNavButtons() {
723
+ document.getElementById('btn-back').disabled = this.historyIndex <= 0;
724
+ document.getElementById('btn-forward').disabled =
725
+ this.historyIndex >= this.history.length - 1;
726
+ },
727
+
728
+ toggleEditMode() {
729
+ this.editMode = !this.editMode;
730
+ const btn = document.getElementById('btn-edit');
731
+ btn.style.background = this.editMode ? '#e94560' : '';
732
+
733
+ document.querySelectorAll('.lion-value[data-key]').forEach(el => {
734
+ el.contentEditable = this.editMode;
735
+ });
736
+
737
+ if (!this.editMode && this.currentData) {
738
+ // Save changes
739
+ this.saveChanges();
740
+ }
741
+ },
742
+
743
+ async saveChanges() {
744
+ const url = this.currentData['@id'];
745
+ if (!url) return;
746
+
747
+ const updates = {};
748
+ document.querySelectorAll('.lion-value[data-key]').forEach(el => {
749
+ const key = el.dataset.key;
750
+ const value = el.textContent.trim();
751
+ updates[key] = value;
752
+ });
753
+
754
+ try {
755
+ await LION.update(url, updates);
756
+ document.getElementById('status-text').textContent = 'Saved!';
757
+ } catch (error) {
758
+ document.getElementById('status-text').textContent = `Save failed: ${error.message}`;
759
+ }
760
+ }
761
+ };
762
+
763
+ window.jsonos = jsonos;
764
+
765
+ // Solid Authentication Module
766
+ const solidAuth = {
767
+ session: null,
768
+ webId: null,
769
+
770
+ async login(idp) {
771
+ try {
772
+ document.getElementById('status-text').textContent = 'Logging in...';
773
+
774
+ // For Electron, we use a popup-based flow
775
+ // This is a simplified version - production would use @inrupt/solid-client-authn-browser
776
+ const authEndpoint = `${idp}/authorize`;
777
+ const clientId = window.location.origin;
778
+
779
+ // Store the IdP for session restoration
780
+ localStorage.setItem('solid-idp', idp);
781
+
782
+ // Open login popup
783
+ const popup = window.open(
784
+ `${idp}/.well-known/openid-configuration`,
785
+ 'solid-login',
786
+ 'width=500,height=600'
787
+ );
788
+
789
+ // For now, show manual WebID input as fallback
790
+ const webId = prompt('Enter your WebID (e.g., https://you.solidcommunity.net/profile/card#me):');
791
+
792
+ if (webId) {
793
+ this.webId = webId;
794
+ this.session = { webId };
795
+ localStorage.setItem('solid-webid', webId);
796
+ this.updateUI(true);
797
+
798
+ // Fetch and display profile
799
+ jsonos.navigate(webId);
800
+ }
801
+ } catch (error) {
802
+ console.error('Login failed:', error);
803
+ document.getElementById('status-text').textContent = `Login failed: ${error.message}`;
804
+ }
805
+ },
806
+
807
+ async logout() {
808
+ this.session = null;
809
+ this.webId = null;
810
+ localStorage.removeItem('solid-webid');
811
+ localStorage.removeItem('solid-idp');
812
+ this.updateUI(false);
813
+ document.getElementById('status-text').textContent = 'Logged out';
814
+ },
815
+
816
+ async restoreSession() {
817
+ const webId = localStorage.getItem('solid-webid');
818
+ if (webId) {
819
+ this.webId = webId;
820
+ this.session = { webId };
821
+ this.updateUI(true);
822
+ return true;
823
+ }
824
+ return false;
825
+ },
826
+
827
+ updateUI(isLoggedIn) {
828
+ const authStatus = document.getElementById('auth-status');
829
+ if (isLoggedIn) {
830
+ authStatus.classList.remove('logged-out');
831
+ authStatus.classList.add('logged-in');
832
+ authStatus.textContent = this.webId.split('/').pop().replace('#me', '') || 'Logged In';
833
+ authStatus.title = this.webId;
834
+ } else {
835
+ authStatus.classList.remove('logged-in');
836
+ authStatus.classList.add('logged-out');
837
+ authStatus.textContent = 'Login';
838
+ authStatus.title = '';
839
+ }
840
+ },
841
+
842
+ // Get fetch with auth headers
843
+ async authFetch(url, options = {}) {
844
+ // In a full implementation, this would add Bearer token
845
+ // For now, just pass through with credentials
846
+ return fetch(url, {
847
+ ...options,
848
+ credentials: 'include'
849
+ });
850
+ }
851
+ };
852
+
853
+ window.solidAuth = solidAuth;
854
+
855
+ // Register some default panes
856
+ jsonos.registerPane('schema:Person', (data) => `
857
+ <div class="lion-object">
858
+ <div class="lion-type">👤 Person</div>
859
+ <div class="lion-property">
860
+ <span class="lion-key">Name</span>
861
+ <span class="lion-value" data-key="schema:name">${data['schema:name'] || data.name || 'Unknown'}</span>
862
+ </div>
863
+ ${data['schema:email'] || data.email ? `
864
+ <div class="lion-property">
865
+ <span class="lion-key">Email</span>
866
+ <span class="lion-value" data-key="schema:email">${data['schema:email'] || data.email}</span>
867
+ </div>` : ''}
868
+ ${data['schema:knows'] || data.knows ? `
869
+ <div class="lion-property">
870
+ <span class="lion-key">Knows</span>
871
+ <span class="lion-value">${jsonos.valueToHtml(data['schema:knows'] || data.knows, 0)}</span>
872
+ </div>` : ''}
873
+ </div>
874
+ `);
875
+
876
+ jsonos.registerPane('schema:WebPage', (data) => `
877
+ <div class="lion-object">
878
+ <div class="lion-type">🌐 Web Page</div>
879
+ <div class="lion-property">
880
+ <span class="lion-key">Title</span>
881
+ <span class="lion-value">${data['schema:name'] || data.name || 'Untitled'}</span>
882
+ </div>
883
+ ${data['schema:description'] ? `
884
+ <div class="lion-property">
885
+ <span class="lion-key">Description</span>
886
+ <span class="lion-value">${data['schema:description']}</span>
887
+ </div>` : ''}
888
+ </div>
889
+ `);
890
+
891
+ // Initialize UI
892
+ document.addEventListener('DOMContentLoaded', () => {
893
+ const urlBar = document.getElementById('url-bar');
894
+ const btnGo = document.getElementById('btn-go');
895
+ const btnBack = document.getElementById('btn-back');
896
+ const btnForward = document.getElementById('btn-forward');
897
+ const btnReload = document.getElementById('btn-reload');
898
+ const btnEdit = document.getElementById('btn-edit');
899
+ const authStatus = document.getElementById('auth-status');
900
+
901
+ // Navigation
902
+ btnGo.addEventListener('click', () => jsonos.navigate(urlBar.value));
903
+ urlBar.addEventListener('keypress', (e) => {
904
+ if (e.key === 'Enter') jsonos.navigate(urlBar.value);
905
+ });
906
+
907
+ btnBack.addEventListener('click', () => jsonos.goBack());
908
+ btnForward.addEventListener('click', () => jsonos.goForward());
909
+ btnReload.addEventListener('click', () => {
910
+ if (jsonos.currentData && jsonos.currentData['@id']) {
911
+ LION.cache.delete(jsonos.currentData['@id']);
912
+ jsonos.navigateWithoutHistory(jsonos.currentData['@id']);
913
+ }
914
+ });
915
+
916
+ // Edit mode
917
+ btnEdit.addEventListener('click', () => jsonos.toggleEditMode());
918
+
919
+ // View tabs
920
+ document.querySelectorAll('#view-tabs button').forEach(btn => {
921
+ btn.addEventListener('click', () => {
922
+ document.querySelectorAll('#view-tabs button').forEach(b => b.classList.remove('active'));
923
+ btn.classList.add('active');
924
+
925
+ const view = btn.dataset.view;
926
+ document.getElementById('viewer').style.display = view === 'rendered' ? 'block' : 'none';
927
+ document.getElementById('raw-json').style.display = view === 'raw' ? 'block' : 'none';
928
+ document.getElementById('custom-view').style.display = view === 'custom' ? 'block' : 'none';
929
+ });
930
+ });
931
+
932
+ // Solid Authentication
933
+ const loginModal = document.getElementById('login-modal');
934
+ const idpInput = document.getElementById('idp-input');
935
+
936
+ authStatus.addEventListener('click', () => {
937
+ if (authStatus.classList.contains('logged-out')) {
938
+ loginModal.classList.add('active');
939
+ } else {
940
+ // Logout
941
+ solidAuth.logout();
942
+ }
943
+ });
944
+
945
+ // Provider buttons
946
+ document.querySelectorAll('.provider-btn').forEach(btn => {
947
+ btn.addEventListener('click', () => {
948
+ idpInput.value = btn.dataset.idp;
949
+ });
950
+ });
951
+
952
+ // Cancel login
953
+ document.getElementById('btn-cancel-login').addEventListener('click', () => {
954
+ loginModal.classList.remove('active');
955
+ });
956
+
957
+ // Do login
958
+ document.getElementById('btn-do-login').addEventListener('click', async () => {
959
+ const idp = idpInput.value.trim();
960
+ if (idp) {
961
+ await solidAuth.login(idp);
962
+ loginModal.classList.remove('active');
963
+ }
964
+ });
965
+
966
+ // Close modal on outside click
967
+ loginModal.addEventListener('click', (e) => {
968
+ if (e.target === loginModal) {
969
+ loginModal.classList.remove('active');
970
+ }
971
+ });
972
+
973
+ // Initial navigation buttons state
974
+ jsonos.updateNavButtons();
975
+
976
+ // Restore session and load initial URL
977
+ solidAuth.restoreSession().then(restored => {
978
+ const params = new URLSearchParams(window.location.search);
979
+ let initialUrl = params.get('uri');
980
+
981
+ if (!initialUrl) {
982
+ if (restored && solidAuth.webId) {
983
+ // Load user's profile
984
+ initialUrl = solidAuth.webId;
985
+ } else {
986
+ // Show welcome/demo content
987
+ initialUrl = 'https://melvincarvalho.com/.well-known/did.json';
988
+ }
989
+ }
990
+
991
+ urlBar.value = initialUrl;
992
+ jsonos.navigate(initialUrl);
993
+ });
994
+ });
995
+ </script>
996
+ </body>
997
+ </html>