sol-components 2.1.0

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.
Files changed (150) hide show
  1. package/README.md +7 -0
  2. package/core/activate.js +27 -0
  3. package/core/adopt.js +71 -0
  4. package/core/auth-core.js +73 -0
  5. package/core/auth-fetch.js +154 -0
  6. package/core/component-mount.js +110 -0
  7. package/core/defaults.js +48 -0
  8. package/core/define.js +15 -0
  9. package/core/display-target.js +166 -0
  10. package/core/edit-placements.js +28 -0
  11. package/core/editor-self.js +127 -0
  12. package/core/editor.js +162 -0
  13. package/core/events.js +27 -0
  14. package/core/extension-points.js +189 -0
  15. package/core/form-utils.js +210 -0
  16. package/core/from-query.js +138 -0
  17. package/core/from-rdf.js +52 -0
  18. package/core/here.js +33 -0
  19. package/core/include-core.js +73 -0
  20. package/core/inrupt-global.js +18 -0
  21. package/core/menu-consumer.js +41 -0
  22. package/core/menu-rdf.js +154 -0
  23. package/core/pod-ops.js +392 -0
  24. package/core/pod-registry.js +82 -0
  25. package/core/popup-proxy.js +255 -0
  26. package/core/rdf-core.js +280 -0
  27. package/core/rdf-render.js +136 -0
  28. package/core/rdf-utils.js +411 -0
  29. package/core/rdf.js +154 -0
  30. package/core/services.js +106 -0
  31. package/core/shape-to-form.js +741 -0
  32. package/core/sparql-safety.js +20 -0
  33. package/core/utils.js +196 -0
  34. package/dist/importmap-cdn.json +49 -0
  35. package/dist/importmap-local.json +49 -0
  36. package/dist/sol-loader.manifest.json +140 -0
  37. package/dist/vendor/@comunica-query-sparql.js +137851 -0
  38. package/dist/vendor/@inrupt-solid-client-authn-browser.js +7503 -0
  39. package/dist/vendor/dompurify.js +1476 -0
  40. package/dist/vendor/ical.js.js +9739 -0
  41. package/dist/vendor/marked.js +85 -0
  42. package/dist/vendor/n3.js +14670 -0
  43. package/dist/vendor/rdf-validate-shacl.js +6970 -0
  44. package/dist/vendor/rdflib.js +35172 -0
  45. package/dist/vendor/solid-logic.js +6819 -0
  46. package/dist/vendor/solid-ui.js +21945 -0
  47. package/node/sol-form.js +133 -0
  48. package/node/sol-include.js +55 -0
  49. package/node/sol-login.js +632 -0
  50. package/node/sol-menu.js +639 -0
  51. package/node/sol-query.js +116 -0
  52. package/package.json +133 -0
  53. package/web/menu-from-rdf.js +23 -0
  54. package/web/scripts/prefs.js +25 -0
  55. package/web/sol-accordion.js +114 -0
  56. package/web/sol-basic.js +50 -0
  57. package/web/sol-breadcrumb.js +131 -0
  58. package/web/sol-button.js +244 -0
  59. package/web/sol-calendar.js +465 -0
  60. package/web/sol-default.js +118 -0
  61. package/web/sol-dropdown-button.js +222 -0
  62. package/web/sol-feed.js +1336 -0
  63. package/web/sol-form.js +949 -0
  64. package/web/sol-full.js +43 -0
  65. package/web/sol-gallery.js +303 -0
  66. package/web/sol-include.js +246 -0
  67. package/web/sol-live-edit.js +415 -0
  68. package/web/sol-login.js +856 -0
  69. package/web/sol-menu.js +593 -0
  70. package/web/sol-modal.js +377 -0
  71. package/web/sol-pod-extras.js +17 -0
  72. package/web/sol-pod-ops.js +680 -0
  73. package/web/sol-pod.js +1039 -0
  74. package/web/sol-query.js +546 -0
  75. package/web/sol-rolodex.js +95 -0
  76. package/web/sol-search.js +402 -0
  77. package/web/sol-settings.js +199 -0
  78. package/web/sol-solidos.js +93 -0
  79. package/web/sol-tabs.js +445 -0
  80. package/web/sol-time.js +194 -0
  81. package/web/sol-tree-edit.js +492 -0
  82. package/web/sol-wac.js +456 -0
  83. package/web/sol-weather.js +337 -0
  84. package/web/sol-window.js +142 -0
  85. package/web/styles/buttons-css.js +108 -0
  86. package/web/styles/help.css +242 -0
  87. package/web/styles/root.css +112 -0
  88. package/web/styles/sol-accordion-css.js +97 -0
  89. package/web/styles/sol-calendar-css.js +154 -0
  90. package/web/styles/sol-feed-css.js +475 -0
  91. package/web/styles/sol-form-css.js +471 -0
  92. package/web/styles/sol-gallery-css.js +181 -0
  93. package/web/styles/sol-include-css.js +95 -0
  94. package/web/styles/sol-live-edit-css.js +84 -0
  95. package/web/styles/sol-live-edit.css +101 -0
  96. package/web/styles/sol-login-css.js +116 -0
  97. package/web/styles/sol-menu-css.js +145 -0
  98. package/web/styles/sol-modal-css.js +134 -0
  99. package/web/styles/sol-pod-css.js +187 -0
  100. package/web/styles/sol-pod-modal-css.js +203 -0
  101. package/web/styles/sol-query-css.js +140 -0
  102. package/web/styles/sol-query-help.css +267 -0
  103. package/web/styles/sol-query-one-pager.css +67 -0
  104. package/web/styles/sol-search-css.js +157 -0
  105. package/web/styles/sol-solidos-css.js +7 -0
  106. package/web/styles/sol-tabs-css.js +114 -0
  107. package/web/styles/sol-time-css.js +30 -0
  108. package/web/styles/sol-wac-css.js +73 -0
  109. package/web/styles/sol-weather-css.js +59 -0
  110. package/web/styles/solid-logo.svg +9 -0
  111. package/web/styles/view-accordion-css.js +66 -0
  112. package/web/styles/view-anchorlist-css.js +22 -0
  113. package/web/styles/view-autocomplete-css.js +59 -0
  114. package/web/styles/view-rolodex-css.js +102 -0
  115. package/web/styles/view-select-css.js +21 -0
  116. package/web/utils/calendar-fetch.js +388 -0
  117. package/web/utils/code-mirror-editor.js +82 -0
  118. package/web/utils/commons-fetch.js +108 -0
  119. package/web/utils/feed-edit.js +159 -0
  120. package/web/utils/feed-edit.smoke.mjs +74 -0
  121. package/web/utils/feed-fetch.js +573 -0
  122. package/web/utils/live-edit-help/csv.js +64 -0
  123. package/web/utils/live-edit-help/graphviz.js +41 -0
  124. package/web/utils/live-edit-help/jsonld.js +55 -0
  125. package/web/utils/live-edit-help/markdown.js +52 -0
  126. package/web/utils/live-edit-help/mermaid.js +48 -0
  127. package/web/utils/live-edit-help/turtle.js +85 -0
  128. package/web/utils/rdf-config.js +125 -0
  129. package/web/utils/renderers/csv.js +124 -0
  130. package/web/utils/renderers/d3-force.js +82 -0
  131. package/web/utils/renderers/graphviz.js +13 -0
  132. package/web/utils/renderers/html.js +10 -0
  133. package/web/utils/renderers/jsonld.js +63 -0
  134. package/web/utils/renderers/markdown.js +19 -0
  135. package/web/utils/renderers/mermaid.js +54 -0
  136. package/web/utils/renderers/turtle.js +51 -0
  137. package/web/utils/sol-query-triple-patterns.js +151 -0
  138. package/web/utils/sol-query-ui.js +250 -0
  139. package/web/utils/sol-query-views.js +32 -0
  140. package/web/views/_helpers.js +34 -0
  141. package/web/views/accordion.js +133 -0
  142. package/web/views/anchorlist.js +59 -0
  143. package/web/views/auto-complete.js +183 -0
  144. package/web/views/dl.js +38 -0
  145. package/web/views/list.js +19 -0
  146. package/web/views/menu.js +56 -0
  147. package/web/views/rolodex.js +126 -0
  148. package/web/views/select.js +79 -0
  149. package/web/views/table.js +73 -0
  150. package/web/views/tabs.js +57 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * feed-edit.js — sol-feed's editing operations over a SKOS/DCAT feeds doc.
3
+ *
4
+ * Each operation is a *pure builder* returning `{ deletes, inserts }` arrays of
5
+ * Turtle triple strings; `patchDoc` turns one into a `application/sparql-update`
6
+ * body and PATCHes it to the feeds file (same-origin; no proxy — the CORS proxy
7
+ * is only for cross-origin RSS reads). Splitting build from send keeps the body
8
+ * construction unit-testable without a network.
9
+ *
10
+ * Model (matches libraries/news/feeds.ttl): topics are `skos:Concept`
11
+ * (`skos:prefLabel`, `skos:topConceptOf` the scheme); sources are
12
+ * `dcat:Dataset, rss:channel` with `dct:title` + `dcat:accessURL` +
13
+ * `dcat:theme` → a topic; the `dcat:Catalog` lists every source via
14
+ * `dcat:dataset`. Deleted sources are re-themed to a reserved `#Deleted`
15
+ * concept (NOT `topConceptOf` the scheme, so it never shows as a normal topic).
16
+ */
17
+
18
+ export const NS = {
19
+ rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
20
+ dct: 'http://purl.org/dc/terms/',
21
+ dcat: 'http://www.w3.org/ns/dcat#',
22
+ skos: 'http://www.w3.org/2004/02/skos/core#',
23
+ rss: 'http://purl.org/rss/1.0/',
24
+ schema: 'http://schema.org/',
25
+ };
26
+
27
+ const DEFAULT_PREFIXES = {
28
+ rdf: NS.rdf, dct: NS.dct, dcat: NS.dcat, skos: NS.skos, rss: NS.rss, schema: NS.schema,
29
+ };
30
+
31
+ /** The reserved deleted-bin concept fragment. */
32
+ export const BIN_FRAG = 'Deleted';
33
+ export const binUriFor = (fileUri) => `${fileUri}#${BIN_FRAG}`;
34
+
35
+ /** A Turtle string literal (handles quotes / backslashes / newlines). */
36
+ export const lit = (s) => JSON.stringify(String(s));
37
+
38
+ /* ── pure builders → { deletes, inserts } ───────────────────────────────── */
39
+
40
+ export function renameTopicEdit(topicUri, oldLabel, newLabel) {
41
+ return {
42
+ deletes: [`<${topicUri}> skos:prefLabel ${lit(oldLabel)} .`],
43
+ inserts: [`<${topicUri}> skos:prefLabel ${lit(newLabel)} .`],
44
+ };
45
+ }
46
+
47
+ export function recategorizeEdit(feedUri, fromTopicUri, toTopicUri) {
48
+ if (fromTopicUri === toTopicUri) return { deletes: [], inserts: [] };
49
+ return {
50
+ deletes: [`<${feedUri}> dcat:theme <${fromTopicUri}> .`],
51
+ inserts: [`<${feedUri}> dcat:theme <${toTopicUri}> .`],
52
+ };
53
+ }
54
+
55
+ export function addFeedEdit(feedUri, { title, url, topicUri, catalogUri }) {
56
+ const inserts = [
57
+ `<${feedUri}> a dcat:Dataset, rss:channel ; ` +
58
+ `dct:title ${lit(title)} ; dcat:accessURL <${url}> ; dcat:theme <${topicUri}> .`,
59
+ ];
60
+ if (catalogUri) inserts.push(`<${catalogUri}> dcat:dataset <${feedUri}> .`);
61
+ return { deletes: [], inserts };
62
+ }
63
+
64
+ /** Move a source to the bin (re-theme to #Deleted), creating the bin concept
65
+ * on first use. `binLabel` only matters when the concept is minted. */
66
+ export function deleteToBinEdit(feedUri, fromTopicUri, binUri, { ensureBin = true, binLabel = 'Deleted' } = {}) {
67
+ const inserts = [`<${feedUri}> dcat:theme <${binUri}> .`];
68
+ if (ensureBin) inserts.push(`<${binUri}> a skos:Concept ; skos:prefLabel ${lit(binLabel)} .`);
69
+ return { deletes: [`<${feedUri}> dcat:theme <${fromTopicUri}> .`], inserts };
70
+ }
71
+
72
+ /** Restore from the bin = re-categorize out of #Deleted to a chosen topic. */
73
+ export function restoreEdit(feedUri, binUri, toTopicUri) {
74
+ return recategorizeEdit(feedUri, binUri, toTopicUri);
75
+ }
76
+
77
+ /** Set/replace a single subject's ordering position. */
78
+ export function reorderEdit(subjectUri, oldPos, newPos) {
79
+ const deletes = oldPos == null ? [] : [`<${subjectUri}> schema:position ${Number(oldPos)} .`];
80
+ return { deletes, inserts: [`<${subjectUri}> schema:position ${Number(newPos)} .`] };
81
+ }
82
+
83
+ /**
84
+ * Re-number a whole ordered list (a topic's sources). Writes `schema:position`
85
+ * 0..n-1 in the new order; only emits triples for items whose position changed.
86
+ * @param {string[]} orderedUris subjects in their new order
87
+ * @param {Object<string,number>} [oldPos] uri → its current position (if any)
88
+ */
89
+ export function setPositionsEdit(orderedUris, oldPos = {}) {
90
+ const deletes = [], inserts = [];
91
+ orderedUris.forEach((uri, i) => {
92
+ const had = oldPos[uri];
93
+ if (had === i) return; // already correct
94
+ if (had != null) deletes.push(`<${uri}> schema:position ${Number(had)} .`);
95
+ inserts.push(`<${uri}> schema:position ${i} .`);
96
+ });
97
+ return { deletes, inserts };
98
+ }
99
+
100
+ /* ── slug / mint ─────────────────────────────────────────────────────────── */
101
+
102
+ /** Mint a unique `<fileUri>#feed-<slug>` not colliding with `existingUris`. */
103
+ export function mintFeedUri(fileUri, title, existingUris = []) {
104
+ const taken = new Set(existingUris);
105
+ const base = 'feed-' + String(title).trim().toLowerCase()
106
+ .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'feed';
107
+ let frag = base, n = 2;
108
+ while (taken.has(`${fileUri}#${frag}`)) frag = `${base}-${n++}`;
109
+ return `${fileUri}#${frag}`;
110
+ }
111
+
112
+ /* ── serialise + send ────────────────────────────────────────────────────── */
113
+
114
+ export function patchBody({ deletes = [], inserts = [] }, prefixes = DEFAULT_PREFIXES) {
115
+ const pfx = Object.entries(prefixes).map(([k, v]) => `PREFIX ${k}: <${v}>`).join('\n');
116
+ const block = (kw, triples) => `${kw} {\n${triples.map((t) => ' ' + t).join('\n')}\n}`;
117
+ const parts = [];
118
+ if (deletes.length) parts.push(block('DELETE DATA', deletes));
119
+ if (inserts.length) parts.push(block('INSERT DATA', inserts));
120
+ return `${pfx}\n${parts.join(' ;\n')}\n`;
121
+ }
122
+
123
+ /**
124
+ * PATCH the feeds file with one edit. Same-origin, no proxy.
125
+ * @param {string} fileUri
126
+ * @param {{deletes?:string[],inserts?:string[]}} edit
127
+ * @param {{prefixes?:object, fetchImpl?:Function}} [opts]
128
+ */
129
+ export async function patchDoc(fileUri, edit, { prefixes = DEFAULT_PREFIXES, fetchImpl } = {}) {
130
+ if (!edit || (!edit.deletes?.length && !edit.inserts?.length)) return;
131
+ const f = fetchImpl || fetch;
132
+ const resp = await f(fileUri, {
133
+ method: 'PATCH',
134
+ headers: { 'Content-Type': 'application/sparql-update' },
135
+ body: patchBody(edit, prefixes),
136
+ });
137
+ if (!resp.ok) throw new Error(`Save failed (HTTP ${resp.status}) — the feeds file must be writable.`);
138
+ }
139
+
140
+ /**
141
+ * Permanent delete: remove ALL of a source's triples (DELETE … WHERE, so it
142
+ * works whatever predicates it has — title, accessURL, theme, position, …)
143
+ * plus its `dcat:dataset` catalog membership. Used to purge from the bin.
144
+ */
145
+ export function purgeBody(feedUri, catalogUri, prefixes = DEFAULT_PREFIXES) {
146
+ const pfx = Object.entries(prefixes).map(([k, v]) => `PREFIX ${k}: <${v}>`).join('\n');
147
+ const cat = catalogUri ? `\n <${catalogUri}> dcat:dataset <${feedUri}> .` : '';
148
+ return `${pfx}\nDELETE {\n <${feedUri}> ?p ?o .${cat}\n} WHERE {\n <${feedUri}> ?p ?o .\n}\n`;
149
+ }
150
+
151
+ export async function purgeFeed(fileUri, feedUri, { catalogUri, prefixes, fetchImpl } = {}) {
152
+ const f = fetchImpl || fetch;
153
+ const resp = await f(fileUri, {
154
+ method: 'PATCH',
155
+ headers: { 'Content-Type': 'application/sparql-update' },
156
+ body: purgeBody(feedUri, catalogUri, prefixes),
157
+ });
158
+ if (!resp.ok) throw new Error(`Permanent delete failed (HTTP ${resp.status}).`);
159
+ }
@@ -0,0 +1,74 @@
1
+ import assert from 'node:assert';
2
+ import {
3
+ renameTopicEdit, recategorizeEdit, addFeedEdit, deleteToBinEdit, restoreEdit,
4
+ reorderEdit, mintFeedUri, patchBody, patchDoc, binUriFor, lit,
5
+ } from './feed-edit.js';
6
+
7
+ let n = 0; const ok = (m) => { console.log(' ✓ ' + m); n++; };
8
+ const F = 'http://ex/feeds.ttl';
9
+
10
+ // rename
11
+ let e = renameTopicEdit(F+'#News', 'News', 'World News');
12
+ assert.deepEqual(e.deletes, [`<${F}#News> skos:prefLabel "News" .`]);
13
+ assert.deepEqual(e.inserts, [`<${F}#News> skos:prefLabel "World News" .`]);
14
+ ok('renameTopicEdit swaps prefLabel');
15
+
16
+ // recategorize + no-op
17
+ e = recategorizeEdit(F+'#f1', F+'#News', F+'#Culture');
18
+ assert.ok(e.deletes[0].includes('#News') && e.inserts[0].includes('#Culture'));
19
+ assert.deepEqual(recategorizeEdit(F+'#f1', F+'#News', F+'#News'), {deletes:[],inserts:[]});
20
+ ok('recategorizeEdit moves dcat:theme (no-op when unchanged)');
21
+
22
+ // add (with + without catalog)
23
+ e = addFeedEdit(F+'#feed-x', { title:'X "Y"', url:'http://x/r.xml', topicUri:F+'#News', catalogUri:F+'#catalog' });
24
+ assert.ok(e.inserts[0].includes('a dcat:Dataset, rss:channel'));
25
+ assert.ok(e.inserts[0].includes('dct:title "X \\"Y\\""')); // quote escaped
26
+ assert.ok(e.inserts[0].includes('dcat:accessURL <http://x/r.xml>'));
27
+ assert.equal(e.inserts[1], `<${F}#catalog> dcat:dataset <${F}#feed-x> .`);
28
+ ok('addFeedEdit builds dataset + catalog membership, escapes literals');
29
+
30
+ // delete → bin (ensures bin concept)
31
+ const bin = binUriFor(F);
32
+ assert.equal(bin, F+'#Deleted');
33
+ e = deleteToBinEdit(F+'#f1', F+'#News', bin);
34
+ assert.deepEqual(e.deletes, [`<${F}#f1> dcat:theme <${bin}> .`].map(()=>`<${F}#f1> dcat:theme <${F}#News> .`));
35
+ assert.ok(e.inserts.some(t => t === `<${F}#f1> dcat:theme <${bin}> .`));
36
+ assert.ok(e.inserts.some(t => t.includes('skos:Concept') && t.includes('"Deleted"')));
37
+ ok('deleteToBinEdit re-themes to #Deleted and mints the bin concept');
38
+
39
+ // restore = recategorize out of bin
40
+ e = restoreEdit(F+'#f1', bin, F+'#Culture');
41
+ assert.ok(e.deletes[0].includes('#Deleted') && e.inserts[0].includes('#Culture'));
42
+ ok('restoreEdit re-files out of the bin');
43
+
44
+ // reorder
45
+ e = reorderEdit(F+'#f1', 3, 7);
46
+ assert.deepEqual(e.deletes, [`<${F}#f1> schema:position 3 .`]);
47
+ assert.deepEqual(e.inserts, [`<${F}#f1> schema:position 7 .`]);
48
+ ok('reorderEdit replaces schema:position');
49
+
50
+ // mint uniqueness
51
+ assert.equal(mintFeedUri(F, 'NY Times', []), F+'#feed-ny-times');
52
+ assert.equal(mintFeedUri(F, 'NY Times', [F+'#feed-ny-times']), F+'#feed-ny-times-2');
53
+ ok('mintFeedUri slugifies + avoids collisions');
54
+
55
+ // patchBody both sections + prefixes
56
+ const body = patchBody(recategorizeEdit(F+'#f1', F+'#News', F+'#Culture'));
57
+ assert.ok(body.includes('PREFIX dcat: <http://www.w3.org/ns/dcat#>'));
58
+ assert.ok(body.includes('DELETE DATA {') && body.includes('INSERT DATA {') && body.includes(' ;\n'));
59
+ assert.ok(patchBody({inserts:['<a> <b> <c> .']}).includes('INSERT DATA') && !patchBody({inserts:['<a> <b> <c> .']}).includes('DELETE'));
60
+ ok('patchBody emits prefixes + DELETE/INSERT DATA blocks');
61
+
62
+ // patchDoc wiring (fake fetch) + no-op skip
63
+ let captured = null;
64
+ await patchDoc(F, recategorizeEdit(F+'#f1', F+'#News', F+'#Culture'), { fetchImpl: async (u, o) => { captured = {u,o}; return {ok:true}; } });
65
+ assert.equal(captured.u, F);
66
+ assert.equal(captured.o.method, 'PATCH');
67
+ assert.equal(captured.o.headers['Content-Type'], 'application/sparql-update');
68
+ let called = false;
69
+ await patchDoc(F, {deletes:[],inserts:[]}, { fetchImpl: async () => { called = true; return {ok:true}; } });
70
+ assert.equal(called, false, 'no-op edit does not PATCH');
71
+ await assert.rejects(() => patchDoc(F, recategorizeEdit(F+'#f1',F+'#a',F+'#b'), { fetchImpl: async()=>({ok:false,status:403}) }), /403/);
72
+ ok('patchDoc PATCHes sparql-update, skips no-ops, throws on !ok');
73
+
74
+ console.log(`\n${n} feed-edit checks passed`);