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.
- package/README.md +7 -0
- package/core/activate.js +27 -0
- package/core/adopt.js +71 -0
- package/core/auth-core.js +73 -0
- package/core/auth-fetch.js +154 -0
- package/core/component-mount.js +110 -0
- package/core/defaults.js +48 -0
- package/core/define.js +15 -0
- package/core/display-target.js +166 -0
- package/core/edit-placements.js +28 -0
- package/core/editor-self.js +127 -0
- package/core/editor.js +162 -0
- package/core/events.js +27 -0
- package/core/extension-points.js +189 -0
- package/core/form-utils.js +210 -0
- package/core/from-query.js +138 -0
- package/core/from-rdf.js +52 -0
- package/core/here.js +33 -0
- package/core/include-core.js +73 -0
- package/core/inrupt-global.js +18 -0
- package/core/menu-consumer.js +41 -0
- package/core/menu-rdf.js +154 -0
- package/core/pod-ops.js +392 -0
- package/core/pod-registry.js +82 -0
- package/core/popup-proxy.js +255 -0
- package/core/rdf-core.js +280 -0
- package/core/rdf-render.js +136 -0
- package/core/rdf-utils.js +411 -0
- package/core/rdf.js +154 -0
- package/core/services.js +106 -0
- package/core/shape-to-form.js +741 -0
- package/core/sparql-safety.js +20 -0
- package/core/utils.js +196 -0
- package/dist/importmap-cdn.json +49 -0
- package/dist/importmap-local.json +49 -0
- package/dist/sol-loader.manifest.json +140 -0
- package/dist/vendor/@comunica-query-sparql.js +137851 -0
- package/dist/vendor/@inrupt-solid-client-authn-browser.js +7503 -0
- package/dist/vendor/dompurify.js +1476 -0
- package/dist/vendor/ical.js.js +9739 -0
- package/dist/vendor/marked.js +85 -0
- package/dist/vendor/n3.js +14670 -0
- package/dist/vendor/rdf-validate-shacl.js +6970 -0
- package/dist/vendor/rdflib.js +35172 -0
- package/dist/vendor/solid-logic.js +6819 -0
- package/dist/vendor/solid-ui.js +21945 -0
- package/node/sol-form.js +133 -0
- package/node/sol-include.js +55 -0
- package/node/sol-login.js +632 -0
- package/node/sol-menu.js +639 -0
- package/node/sol-query.js +116 -0
- package/package.json +133 -0
- package/web/menu-from-rdf.js +23 -0
- package/web/scripts/prefs.js +25 -0
- package/web/sol-accordion.js +114 -0
- package/web/sol-basic.js +50 -0
- package/web/sol-breadcrumb.js +131 -0
- package/web/sol-button.js +244 -0
- package/web/sol-calendar.js +465 -0
- package/web/sol-default.js +118 -0
- package/web/sol-dropdown-button.js +222 -0
- package/web/sol-feed.js +1336 -0
- package/web/sol-form.js +949 -0
- package/web/sol-full.js +43 -0
- package/web/sol-gallery.js +303 -0
- package/web/sol-include.js +246 -0
- package/web/sol-live-edit.js +415 -0
- package/web/sol-login.js +856 -0
- package/web/sol-menu.js +593 -0
- package/web/sol-modal.js +377 -0
- package/web/sol-pod-extras.js +17 -0
- package/web/sol-pod-ops.js +680 -0
- package/web/sol-pod.js +1039 -0
- package/web/sol-query.js +546 -0
- package/web/sol-rolodex.js +95 -0
- package/web/sol-search.js +402 -0
- package/web/sol-settings.js +199 -0
- package/web/sol-solidos.js +93 -0
- package/web/sol-tabs.js +445 -0
- package/web/sol-time.js +194 -0
- package/web/sol-tree-edit.js +492 -0
- package/web/sol-wac.js +456 -0
- package/web/sol-weather.js +337 -0
- package/web/sol-window.js +142 -0
- package/web/styles/buttons-css.js +108 -0
- package/web/styles/help.css +242 -0
- package/web/styles/root.css +112 -0
- package/web/styles/sol-accordion-css.js +97 -0
- package/web/styles/sol-calendar-css.js +154 -0
- package/web/styles/sol-feed-css.js +475 -0
- package/web/styles/sol-form-css.js +471 -0
- package/web/styles/sol-gallery-css.js +181 -0
- package/web/styles/sol-include-css.js +95 -0
- package/web/styles/sol-live-edit-css.js +84 -0
- package/web/styles/sol-live-edit.css +101 -0
- package/web/styles/sol-login-css.js +116 -0
- package/web/styles/sol-menu-css.js +145 -0
- package/web/styles/sol-modal-css.js +134 -0
- package/web/styles/sol-pod-css.js +187 -0
- package/web/styles/sol-pod-modal-css.js +203 -0
- package/web/styles/sol-query-css.js +140 -0
- package/web/styles/sol-query-help.css +267 -0
- package/web/styles/sol-query-one-pager.css +67 -0
- package/web/styles/sol-search-css.js +157 -0
- package/web/styles/sol-solidos-css.js +7 -0
- package/web/styles/sol-tabs-css.js +114 -0
- package/web/styles/sol-time-css.js +30 -0
- package/web/styles/sol-wac-css.js +73 -0
- package/web/styles/sol-weather-css.js +59 -0
- package/web/styles/solid-logo.svg +9 -0
- package/web/styles/view-accordion-css.js +66 -0
- package/web/styles/view-anchorlist-css.js +22 -0
- package/web/styles/view-autocomplete-css.js +59 -0
- package/web/styles/view-rolodex-css.js +102 -0
- package/web/styles/view-select-css.js +21 -0
- package/web/utils/calendar-fetch.js +388 -0
- package/web/utils/code-mirror-editor.js +82 -0
- package/web/utils/commons-fetch.js +108 -0
- package/web/utils/feed-edit.js +159 -0
- package/web/utils/feed-edit.smoke.mjs +74 -0
- package/web/utils/feed-fetch.js +573 -0
- package/web/utils/live-edit-help/csv.js +64 -0
- package/web/utils/live-edit-help/graphviz.js +41 -0
- package/web/utils/live-edit-help/jsonld.js +55 -0
- package/web/utils/live-edit-help/markdown.js +52 -0
- package/web/utils/live-edit-help/mermaid.js +48 -0
- package/web/utils/live-edit-help/turtle.js +85 -0
- package/web/utils/rdf-config.js +125 -0
- package/web/utils/renderers/csv.js +124 -0
- package/web/utils/renderers/d3-force.js +82 -0
- package/web/utils/renderers/graphviz.js +13 -0
- package/web/utils/renderers/html.js +10 -0
- package/web/utils/renderers/jsonld.js +63 -0
- package/web/utils/renderers/markdown.js +19 -0
- package/web/utils/renderers/mermaid.js +54 -0
- package/web/utils/renderers/turtle.js +51 -0
- package/web/utils/sol-query-triple-patterns.js +151 -0
- package/web/utils/sol-query-ui.js +250 -0
- package/web/utils/sol-query-views.js +32 -0
- package/web/views/_helpers.js +34 -0
- package/web/views/accordion.js +133 -0
- package/web/views/anchorlist.js +59 -0
- package/web/views/auto-complete.js +183 -0
- package/web/views/dl.js +38 -0
- package/web/views/list.js +19 -0
- package/web/views/menu.js +56 -0
- package/web/views/rolodex.js +126 -0
- package/web/views/select.js +79 -0
- package/web/views/table.js +73 -0
- package/web/views/tabs.js +57 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const jsonldHelp = {
|
|
2
|
+
title: 'JSON-LD Reference',
|
|
3
|
+
sections: [
|
|
4
|
+
{
|
|
5
|
+
heading: 'Core Keywords',
|
|
6
|
+
items: [
|
|
7
|
+
{
|
|
8
|
+
title: '@context',
|
|
9
|
+
description: 'Maps short keys to full URIs.',
|
|
10
|
+
code: `{\n "@context": {\n "name": "http://xmlns.com/foaf/0.1/name",\n "Person": "http://xmlns.com/foaf/0.1/Person"\n }\n}`
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
title: '@id and @type',
|
|
14
|
+
description: 'Identify the resource and its RDF type.',
|
|
15
|
+
code: `{\n "@id": "http://example.org/people/abebe",\n "@type": "foaf:Person",\n "name": "Abebe Girma"\n}`
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: '@graph',
|
|
19
|
+
description: 'Array of multiple resources in one document.',
|
|
20
|
+
code: `{\n "@context": {"name": "foaf:name"},\n "@graph": [\n {"@id": ":catalina", "name": "Catalina Ruiz"},\n {"@id": ":soo-jin", "name": "Soo-Jin Park"}\n ]\n}`
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
heading: 'Linking Resources',
|
|
26
|
+
items: [
|
|
27
|
+
{
|
|
28
|
+
title: 'Object reference',
|
|
29
|
+
description: 'Link to another resource by @id.',
|
|
30
|
+
code: `{\n "@id": ":catalina",\n "knows": {"@id": ":soo-jin"}\n}`
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
title: 'Array of references',
|
|
34
|
+
description: 'Multiple values with @type: @id in context.',
|
|
35
|
+
code: `{\n "@context": {\n "knows": {"@id":"foaf:knows","@type":"@id"}\n },\n "@id": ":nadia",\n "knows": [":catalina", ":layla"]\n}`
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
heading: 'Typed Values',
|
|
41
|
+
items: [
|
|
42
|
+
{
|
|
43
|
+
title: 'Language-tagged strings',
|
|
44
|
+
description: 'Use @language in a value object.',
|
|
45
|
+
code: `{\n "name": {"@value": "Kenji Watanabe", "@language": "en"},\n "altName": {"@value": "渡辺健二", "@language": "ja"}\n}`
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: 'Typed literals',
|
|
49
|
+
description: 'Attach an XSD type to a value.',
|
|
50
|
+
code: `{\n "age": {"@value": "29", "@type": "xsd:integer"},\n "joined": {"@value": "2021-03-15", "@type": "xsd:date"}\n}`
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const markdownHelp = {
|
|
2
|
+
title: 'Markdown Reference',
|
|
3
|
+
sections: [
|
|
4
|
+
{
|
|
5
|
+
heading: 'Text Formatting',
|
|
6
|
+
items: [
|
|
7
|
+
{ title: 'Emphasis', description: 'Italic, bold, or bold-italic text.', code: `*italic* _also italic_\n**bold** __also bold__\n***bold italic***` },
|
|
8
|
+
{ title: 'Inline Code', description: 'Short code spans.', code: 'Use `const x = 5;` inline.' },
|
|
9
|
+
{ title: 'Strikethrough', description: 'Cross out text.', code: '~~removed~~' }
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
heading: 'Headings',
|
|
14
|
+
items: [
|
|
15
|
+
{ title: 'ATX Style', description: '# symbols set heading level 1–6.', code: `# Heading 1\n## Heading 2\n### Heading 3` }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
heading: 'Lists',
|
|
20
|
+
items: [
|
|
21
|
+
{ title: 'Unordered', description: '-, *, or + as bullet markers.', code: `- Amara Okafor — Lagos\n- Priya Sharma — Mumbai\n- Nadia Bintang — Jakarta` },
|
|
22
|
+
{ title: 'Ordered', description: 'Numbers followed by a period.', code: `1. First priority\n2. Second priority\n3. Third priority` },
|
|
23
|
+
{ title: 'Nested', description: 'Indent with two or four spaces.', code: `- Africa\n - Nigeria\n - Kenya\n- Asia\n - India\n - Japan` }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
heading: 'Links & Images',
|
|
28
|
+
items: [
|
|
29
|
+
{ title: 'Link', description: 'Text in brackets, URL in parentheses.', code: `[Visit Example](https://example.com)` },
|
|
30
|
+
{ title: 'Image', description: 'Like a link but prefixed with !', code: `` }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
heading: 'Blocks',
|
|
35
|
+
items: [
|
|
36
|
+
{ title: 'Code Block', description: 'Fence with triple backticks, optionally with language.', code: '```python\nprint("مرحبا بالعالم") # Arabic: Hello World\n```' },
|
|
37
|
+
{ title: 'Blockquote', description: 'Prefix lines with >.', code: `> "We are the ones we have been waiting for."\n> — June Jordan` },
|
|
38
|
+
{ title: 'Horizontal Rule', description: 'Three or more dashes on their own line.', code: `---` }
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
heading: 'Tables',
|
|
43
|
+
items: [
|
|
44
|
+
{
|
|
45
|
+
title: 'GFM Table',
|
|
46
|
+
description: 'Pipes separate columns; the second row sets alignment.',
|
|
47
|
+
code: `| Name | City | Role |\n|------|------|------|\n| Layla Al-Hassan | Amman | Data Scientist |\n| Thien Nguyen | Ho Chi Minh City | Engineer |`
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const mermaidHelp = {
|
|
2
|
+
title: 'Mermaid Diagram Reference',
|
|
3
|
+
sections: [
|
|
4
|
+
{
|
|
5
|
+
heading: 'Flowchart',
|
|
6
|
+
items: [
|
|
7
|
+
{ title: 'Direction', description: 'TD (top-down), LR (left-right), BT, RL.', code: `graph LR\n A[Start] --> B[Process] --> C[End]` },
|
|
8
|
+
{ title: 'Node shapes', description: 'Rectangles, rounded, diamonds, circles.', code: `graph TD\n A[Rectangle]\n B(Rounded)\n C{Diamond}\n D((Circle))\n A --> B --> C --> D` },
|
|
9
|
+
{ title: 'Labels on edges', description: 'Add text to arrows.', code: `graph LR\n Lagos -->|sends PR| Mumbai\n Mumbai -->|approves| Jakarta` }
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
heading: 'Sequence Diagram',
|
|
14
|
+
items: [
|
|
15
|
+
{
|
|
16
|
+
title: 'Participants and messages',
|
|
17
|
+
description: 'Model interactions between named actors.',
|
|
18
|
+
code: `sequenceDiagram\n participant D as Diego (Mexico)\n participant A as Amara (Nigeria)\n D->>A: Pull-request review?\n A-->>D: Approved ✓`
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
title: 'Loops and alternatives',
|
|
22
|
+
description: 'Control flow in sequences.',
|
|
23
|
+
code: `sequenceDiagram\n loop Daily standup\n Priya->>Nadia: Status?\n Nadia-->>Priya: On track\n end`
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
heading: 'Class Diagram',
|
|
29
|
+
items: [
|
|
30
|
+
{
|
|
31
|
+
title: 'Classes and relationships',
|
|
32
|
+
description: 'UML-style class diagram.',
|
|
33
|
+
code: `classDiagram\n class Person {\n +String name\n +String location\n +connect(other)\n }\n Person <|-- Engineer\n Person <|-- Designer`
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
heading: 'Gantt Chart',
|
|
39
|
+
items: [
|
|
40
|
+
{
|
|
41
|
+
title: 'Project timeline',
|
|
42
|
+
description: 'Tasks with start and duration.',
|
|
43
|
+
code: `gantt\n title Q2 Roadmap\n dateFormat YYYY-MM-DD\n section Lagos team\n Feature A :a1, 2024-04-01, 14d\n section Mumbai team\n Feature B :2024-04-08, 21d`
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const turtleHelp = {
|
|
2
|
+
title: 'Turtle RDF Reference',
|
|
3
|
+
sections: [
|
|
4
|
+
{
|
|
5
|
+
heading: 'Prefixes',
|
|
6
|
+
items: [
|
|
7
|
+
{
|
|
8
|
+
title: 'Declare Namespaces',
|
|
9
|
+
description: 'Bind short prefixes to long URI namespaces.',
|
|
10
|
+
code: `@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n@prefix : <http://example.org/> .`
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
heading: 'Triples',
|
|
16
|
+
items: [
|
|
17
|
+
{
|
|
18
|
+
title: 'Subject · Predicate · Object',
|
|
19
|
+
description: 'Each statement has three parts ending with a period.',
|
|
20
|
+
code: `:amara foaf:name "Amara Okafor" .\n:amara foaf:knows :kenji .`
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: 'Type shortcut',
|
|
24
|
+
description: '`a` is shorthand for `rdf:type`.',
|
|
25
|
+
code: `:amara a foaf:Person .`
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: 'Multiple predicates (;)',
|
|
29
|
+
description: 'Semicolons share the same subject.',
|
|
30
|
+
code: `:amara a foaf:Person ;\n foaf:name "Amara Okafor" ;\n foaf:location "Lagos, Nigeria" .`
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
title: 'Multiple objects (,)',
|
|
34
|
+
description: 'Commas share subject and predicate.',
|
|
35
|
+
code: `:amara foaf:knows :kenji, :priya, :layla .`
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
heading: 'Literals',
|
|
41
|
+
items: [
|
|
42
|
+
{
|
|
43
|
+
title: 'Plain string',
|
|
44
|
+
description: 'Text in double quotes.',
|
|
45
|
+
code: `:amara foaf:name "Amara Okafor" .`
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: 'Language tag',
|
|
49
|
+
description: 'Attach a BCP-47 language code.',
|
|
50
|
+
code: `:amara rdfs:label "Amara Okafor"@en .\n:amara rdfs:label "アマラ・オカフォー"@ja .`
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
title: 'Typed literal',
|
|
54
|
+
description: 'Attach an XSD datatype.',
|
|
55
|
+
code: `:amara foaf:age "31"^^xsd:integer .\n:amara schema:birthDate "1993-04-12"^^xsd:date .`
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
heading: 'Common Vocabularies',
|
|
61
|
+
items: [
|
|
62
|
+
{
|
|
63
|
+
title: 'FOAF',
|
|
64
|
+
description: 'Describing people and social links.',
|
|
65
|
+
code: `@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n\n:kenji a foaf:Person ;\n foaf:name "Kenji Watanabe" ;\n foaf:mbox <mailto:kenji@example.org> ;\n foaf:knows :priya .`
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
title: 'Schema.org',
|
|
69
|
+
description: 'Rich metadata for people, places, events.',
|
|
70
|
+
code: `@prefix schema: <https://schema.org/> .\n\n:layla a schema:Person ;\n schema:name "Layla Al-Hassan" ;\n schema:jobTitle "Data Scientist" ;\n schema:addressLocality "Amman" ;\n schema:addressCountry "JO" .`
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
heading: 'Blank Nodes',
|
|
76
|
+
items: [
|
|
77
|
+
{
|
|
78
|
+
title: 'Anonymous resources',
|
|
79
|
+
description: 'Resources without a URI, using [ ] syntax.',
|
|
80
|
+
code: `:abebe foaf:knows [\n a foaf:Person ;\n foaf:name "Yewande Adebayo"\n] .`
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rdf-config.js — read a subject's direct properties from a Turtle
|
|
3
|
+
* file as a flat `{ predicateUri: value | value[] }` JS object.
|
|
4
|
+
*
|
|
5
|
+
* Companion to `feed-fetch.js#parseSourceList`. Config files follow
|
|
6
|
+
* the direct-predicate pattern documented in
|
|
7
|
+
* claude/plans/PLAN-vocab-migration.md:
|
|
8
|
+
*
|
|
9
|
+
* @prefix geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> .
|
|
10
|
+
* @prefix schema: <http://schema.org/> .
|
|
11
|
+
*
|
|
12
|
+
* <#Settings>
|
|
13
|
+
* geo:lat 45.52 ;
|
|
14
|
+
* geo:long -122.68 ;
|
|
15
|
+
* schema:addressLocality "Portland, OR" .
|
|
16
|
+
*
|
|
17
|
+
* resolves to:
|
|
18
|
+
*
|
|
19
|
+
* {
|
|
20
|
+
* 'http://www.w3.org/2003/01/geo/wgs84_pos#lat': 45.52,
|
|
21
|
+
* 'http://www.w3.org/2003/01/geo/wgs84_pos#long': -122.68,
|
|
22
|
+
* 'http://schema.org/addressLocality': "Portland, OR",
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Multi-valued predicates (`dct:source <a>, <b>, <c>`) come back as
|
|
26
|
+
* arrays in document order; single-valued predicates stay scalar.
|
|
27
|
+
*
|
|
28
|
+
* Literal values are typed from their xsd datatype (`true` → boolean,
|
|
29
|
+
* `9` → integer, `45.52` → decimal, anything else → string). NamedNode
|
|
30
|
+
* objects come back as their URI string.
|
|
31
|
+
*
|
|
32
|
+
* Component code is responsible for the predicate URI → its own
|
|
33
|
+
* internal name mapping. See `sol-weather.js` for the canonical
|
|
34
|
+
* example. The reader stays component-agnostic.
|
|
35
|
+
*
|
|
36
|
+
* The previous PropertyValue indirection pattern (schema:additionalProperty
|
|
37
|
+
* → PropertyValue node → schema:name + schema:value) was migrated out
|
|
38
|
+
* in favour of direct predicates. See PLAN-vocab-migration.md for the
|
|
39
|
+
* predicate choices and the rationale.
|
|
40
|
+
*
|
|
41
|
+
* All RDF goes through `core/rdf.js`, so a page that already has rdflib
|
|
42
|
+
* live (via importmap or vendored ESM) shares it here.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { rdf } from '../../core/rdf.js';
|
|
46
|
+
|
|
47
|
+
/** Last URI segment — used only for picking the local name of an xsd
|
|
48
|
+
* datatype URI when typing literal values. */
|
|
49
|
+
function localName(uri) {
|
|
50
|
+
const i = Math.max(uri.lastIndexOf('#'), uri.lastIndexOf('/'));
|
|
51
|
+
return i === -1 ? uri : uri.slice(i + 1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Convert one RDF object node to a JS primitive based on its datatype. */
|
|
55
|
+
function typedValue(node) {
|
|
56
|
+
if (!node) return null;
|
|
57
|
+
if (node.termType === 'NamedNode') return node.value;
|
|
58
|
+
if (node.termType !== 'Literal') return node.value;
|
|
59
|
+
|
|
60
|
+
const local = localName(node.datatype?.value || '');
|
|
61
|
+
switch (local) {
|
|
62
|
+
case 'boolean':
|
|
63
|
+
return node.value === 'true' || node.value === '1';
|
|
64
|
+
case 'integer':
|
|
65
|
+
case 'int':
|
|
66
|
+
case 'long':
|
|
67
|
+
case 'short':
|
|
68
|
+
case 'byte':
|
|
69
|
+
case 'nonNegativeInteger':
|
|
70
|
+
case 'positiveInteger':
|
|
71
|
+
case 'negativeInteger':
|
|
72
|
+
case 'nonPositiveInteger':
|
|
73
|
+
return parseInt(node.value, 10);
|
|
74
|
+
case 'decimal':
|
|
75
|
+
case 'double':
|
|
76
|
+
case 'float':
|
|
77
|
+
return parseFloat(node.value);
|
|
78
|
+
default:
|
|
79
|
+
return node.value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Fetch a TTL file and return one subject's direct properties as a
|
|
85
|
+
* flat JS object keyed by predicate URI.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} sourceUri "file.ttl#Subject" — the fragment is required.
|
|
88
|
+
* @return {Promise<Object>} { predicateUri: value | value[], ... }
|
|
89
|
+
* @throws on HTTP failure, on a missing rdflib, on a missing fragment.
|
|
90
|
+
*/
|
|
91
|
+
export async function loadConfig(sourceUri) {
|
|
92
|
+
const abs = new URL(sourceUri || '', document.baseURI).href;
|
|
93
|
+
const hashIdx = abs.indexOf('#');
|
|
94
|
+
if (hashIdx === -1) {
|
|
95
|
+
throw new Error(`source needs a subject fragment — got "${sourceUri}"`);
|
|
96
|
+
}
|
|
97
|
+
const fileUri = abs.slice(0, hashIdx);
|
|
98
|
+
|
|
99
|
+
const resp = await fetch(fileUri);
|
|
100
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status} fetching ${fileUri}`);
|
|
101
|
+
const text = await resp.text();
|
|
102
|
+
|
|
103
|
+
if (!rdf.isReady()) throw new Error('RDF config needs rdflib on the page');
|
|
104
|
+
const store = rdf.graph();
|
|
105
|
+
rdf.parse(text, store, fileUri, 'text/turtle');
|
|
106
|
+
|
|
107
|
+
const subject = rdf.sym(abs);
|
|
108
|
+
const out = {};
|
|
109
|
+
|
|
110
|
+
// Walk every triple with the requested subject. Group by predicate;
|
|
111
|
+
// arrays accumulate when a predicate has more than one object.
|
|
112
|
+
const append = (predUri, value) => {
|
|
113
|
+
if (Array.isArray(out[predUri])) out[predUri].push(value);
|
|
114
|
+
else if (predUri in out) out[predUri] = [out[predUri], value];
|
|
115
|
+
else out[predUri] = value;
|
|
116
|
+
};
|
|
117
|
+
for (const st of store.statementsMatching(subject, null, null)) {
|
|
118
|
+
// Skip rdf:type — it's a class declaration, not a setting value.
|
|
119
|
+
if (st.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') continue;
|
|
120
|
+
append(st.predicate.value, typedValue(st.object));
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default loadConfig;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
function detectDelimiter(line) {
|
|
2
|
+
for (const d of [',', ';', '\t', '|']) if (line.includes(d)) return d;
|
|
3
|
+
return ',';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function parseCSVLine(line, delim) {
|
|
7
|
+
const cells = [];
|
|
8
|
+
let cur = '', inQ = false;
|
|
9
|
+
for (let i = 0; i < line.length; i++) {
|
|
10
|
+
const c = line[i];
|
|
11
|
+
if (c === '"') {
|
|
12
|
+
if (inQ && line[i + 1] === '"') { cur += '"'; i++; }
|
|
13
|
+
else inQ = !inQ;
|
|
14
|
+
} else if (c === delim && !inQ) {
|
|
15
|
+
cells.push(cur.trim()); cur = '';
|
|
16
|
+
} else cur += c;
|
|
17
|
+
}
|
|
18
|
+
cells.push(cur.trim());
|
|
19
|
+
return cells;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function parseCSV(text) {
|
|
23
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
24
|
+
if (!lines.length) throw new Error('No CSV data');
|
|
25
|
+
const delim = detectDelimiter(lines[0]);
|
|
26
|
+
return lines.map(l => parseCSVLine(l, delim));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderCSV(content, outputEl) {
|
|
30
|
+
const rows = parseCSV(content);
|
|
31
|
+
|
|
32
|
+
const wrap = document.createElement('div');
|
|
33
|
+
wrap.style.cssText = 'overflow:auto;width:100%;height:100%';
|
|
34
|
+
const table = document.createElement('table');
|
|
35
|
+
table.className = 'csv-tbl';
|
|
36
|
+
table.setAttribute('role', 'table');
|
|
37
|
+
|
|
38
|
+
const thead = document.createElement('thead');
|
|
39
|
+
const tbody = document.createElement('tbody');
|
|
40
|
+
|
|
41
|
+
rows.forEach((row, ri) => {
|
|
42
|
+
const tr = document.createElement('tr');
|
|
43
|
+
row.forEach(cell => {
|
|
44
|
+
const el = document.createElement(ri === 0 ? 'th' : 'td');
|
|
45
|
+
const n = Number(cell);
|
|
46
|
+
el.textContent = (cell !== '' && !isNaN(n) && isFinite(n)) ? _fmt(n) : cell;
|
|
47
|
+
tr.appendChild(el);
|
|
48
|
+
});
|
|
49
|
+
(ri === 0 ? thead : tbody).appendChild(tr);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
table.appendChild(thead);
|
|
53
|
+
table.appendChild(tbody);
|
|
54
|
+
wrap.appendChild(table);
|
|
55
|
+
outputEl.innerHTML = '';
|
|
56
|
+
outputEl.appendChild(wrap);
|
|
57
|
+
return rows;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function calcStats(rows) {
|
|
61
|
+
if (rows.length < 2) return null;
|
|
62
|
+
const headers = rows[0];
|
|
63
|
+
const data = rows.slice(1);
|
|
64
|
+
const stats = {};
|
|
65
|
+
|
|
66
|
+
headers.forEach((h, ci) => {
|
|
67
|
+
const col = data.map(r => r[ci] || '').filter(Boolean);
|
|
68
|
+
if (!col.length) { stats[h] = { type: 'empty' }; return; }
|
|
69
|
+
const nums = col.map(Number).filter(v => !isNaN(v) && v !== Infinity);
|
|
70
|
+
if (nums.length === col.length) {
|
|
71
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
72
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
73
|
+
const mid = Math.floor(sorted.length / 2);
|
|
74
|
+
stats[h] = {
|
|
75
|
+
type: 'numeric', count: sorted.length,
|
|
76
|
+
min: sorted[0], max: sorted[sorted.length - 1],
|
|
77
|
+
mean: (sum / sorted.length).toFixed(2),
|
|
78
|
+
median: sorted.length % 2 ? sorted[mid].toFixed(2) : ((sorted[mid - 1] + sorted[mid]) / 2).toFixed(2),
|
|
79
|
+
sum: sum.toFixed(2),
|
|
80
|
+
};
|
|
81
|
+
} else {
|
|
82
|
+
const freq = {};
|
|
83
|
+
col.forEach(v => { freq[v] = (freq[v] || 0) + 1; });
|
|
84
|
+
const entries = Object.entries(freq);
|
|
85
|
+
const maxCount = Math.max(...entries.map(e => e[1]));
|
|
86
|
+
const atMax = entries.filter(e => e[1] === maxCount);
|
|
87
|
+
const mostCommon = atMax.length === 1 ? atMax[0][0] : null;
|
|
88
|
+
stats[h] = { type: 'text', count: col.length, unique: new Set(col).size, mostCommon };
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return stats;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function _fmt(n) {
|
|
95
|
+
return Number(n).toLocaleString('en-US', { maximumFractionDigits: 2 });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function renderStats(stats, containerEl) {
|
|
99
|
+
if (!stats) { containerEl.innerHTML = '<p>Not enough data.</p>'; return; }
|
|
100
|
+
let html = '';
|
|
101
|
+
for (const [h, s] of Object.entries(stats)) {
|
|
102
|
+
html += `<div class="stat-col"><h3>${h}</h3>`;
|
|
103
|
+
if (s.type === 'empty') {
|
|
104
|
+
html += '<p>No data</p>';
|
|
105
|
+
} else if (s.type === 'numeric') {
|
|
106
|
+
html += `<table class="stat-table">
|
|
107
|
+
<tr><td>Count</td><td>${_fmt(s.count)}</td></tr>
|
|
108
|
+
<tr><td>Min</td><td>${_fmt(s.min)}</td></tr>
|
|
109
|
+
<tr><td>Max</td><td>${_fmt(s.max)}</td></tr>
|
|
110
|
+
<tr><td>Sum</td><td>${_fmt(s.sum)}</td></tr>
|
|
111
|
+
<tr><td>Mean</td><td>${_fmt(s.mean)}</td></tr>
|
|
112
|
+
<tr><td>Median</td><td>${_fmt(s.median)}</td></tr>
|
|
113
|
+
</table>`;
|
|
114
|
+
} else {
|
|
115
|
+
html += `<table class="stat-table">
|
|
116
|
+
<tr><td>Count</td><td>${_fmt(s.count)}</td></tr>
|
|
117
|
+
<tr><td>Unique</td><td>${_fmt(s.unique)}</td></tr>
|
|
118
|
+
<tr><td>Most common</td><td>${s.mostCommon != null ? `"${s.mostCommon}"` : '(no unique leader)'}</td></tr>
|
|
119
|
+
</table>`;
|
|
120
|
+
}
|
|
121
|
+
html += '</div>';
|
|
122
|
+
}
|
|
123
|
+
containerEl.innerHTML = html;
|
|
124
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Shared D3 force-directed graph renderer used by turtle and jsonld renderers.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draw a force-directed graph into `container`.
|
|
5
|
+
* @param {Element} container — DOM element to render into
|
|
6
|
+
* @param {{ nodes: Array, links: Array }} graphData
|
|
7
|
+
* nodes: [{ id, label, displayLabel?, properties? }]
|
|
8
|
+
* links: [{ source, target, label }]
|
|
9
|
+
*/
|
|
10
|
+
export async function drawForceGraph(container, graphData) {
|
|
11
|
+
const d3 = await import('https://esm.sh/d3@7');
|
|
12
|
+
|
|
13
|
+
container.innerHTML = '';
|
|
14
|
+
const width = container.clientWidth || 700;
|
|
15
|
+
const height = container.clientHeight || 480;
|
|
16
|
+
|
|
17
|
+
const svg = d3.select(container)
|
|
18
|
+
.append('svg')
|
|
19
|
+
.attr('width', width).attr('height', height)
|
|
20
|
+
.attr('viewBox', [0, 0, width, height])
|
|
21
|
+
.style('width', '100%').style('height', '100%');
|
|
22
|
+
|
|
23
|
+
svg.append('defs').append('marker')
|
|
24
|
+
.attr('id', 'arr').attr('viewBox', '-0 -5 10 10')
|
|
25
|
+
.attr('refX', 26).attr('refY', 0)
|
|
26
|
+
.attr('orient', 'auto').attr('markerWidth', 6).attr('markerHeight', 6)
|
|
27
|
+
.append('path').attr('d', 'M0,-5L10,0L0,5').attr('fill', '#95a5a6');
|
|
28
|
+
|
|
29
|
+
// Resolve string ids → node objects for d3 links
|
|
30
|
+
const nodeById = new Map(graphData.nodes.map(n => [n.id, n]));
|
|
31
|
+
const links = graphData.links.map(l => ({
|
|
32
|
+
...l,
|
|
33
|
+
source: nodeById.get(l.source) || l.source,
|
|
34
|
+
target: nodeById.get(l.target) || l.target,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
const sim = d3.forceSimulation(graphData.nodes)
|
|
38
|
+
.force('link', d3.forceLink(links).id(d => d.id).distance(140))
|
|
39
|
+
.force('charge', d3.forceManyBody().strength(-380))
|
|
40
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
41
|
+
.force('collision', d3.forceCollide(38));
|
|
42
|
+
|
|
43
|
+
const linkG = svg.append('g').attr('fill', 'none').attr('stroke', '#95a5a6').attr('stroke-width', 1.5);
|
|
44
|
+
const linkEl = linkG.selectAll('line').data(links).join('line')
|
|
45
|
+
.attr('marker-end', 'url(#arr)');
|
|
46
|
+
|
|
47
|
+
const linkLabel = svg.append('g').selectAll('text').data(links).join('text')
|
|
48
|
+
.style('font-size', 'var(--font-app)').attr('fill', '#999').attr('text-anchor', 'middle')
|
|
49
|
+
.text(d => d.label);
|
|
50
|
+
|
|
51
|
+
const nodeG = svg.append('g');
|
|
52
|
+
const nodeEl = nodeG.selectAll('g').data(graphData.nodes).join('g')
|
|
53
|
+
.attr('class', 'node')
|
|
54
|
+
.call(d3.drag()
|
|
55
|
+
.on('start', (ev) => { if (!ev.active) sim.alphaTarget(0.3).restart(); ev.subject.fx = ev.subject.x; ev.subject.fy = ev.subject.y; })
|
|
56
|
+
.on('drag', (ev) => { ev.subject.fx = ev.x; ev.subject.fy = ev.y; })
|
|
57
|
+
.on('end', (ev) => { if (!ev.active) sim.alphaTarget(0); ev.subject.fx = null; ev.subject.fy = null; }));
|
|
58
|
+
|
|
59
|
+
nodeEl.append('circle').attr('r', 20).attr('fill', '#4a9eff').attr('fill-opacity', 0.8).attr('stroke', '#fff').attr('stroke-width', 2);
|
|
60
|
+
|
|
61
|
+
nodeEl.append('text').attr('dy', 34).attr('text-anchor', 'middle')
|
|
62
|
+
.style('font-size', 'calc(var(--font-app) * 1.2)')
|
|
63
|
+
.each(function(d) {
|
|
64
|
+
const t = d3.select(this);
|
|
65
|
+
const label = d.displayLabel || d.label;
|
|
66
|
+
t.append('tspan').attr('x', 0).attr('font-weight', d.displayLabel ? 'bold' : 'normal').text(label);
|
|
67
|
+
(d.properties || []).slice(0, 2).forEach((p, i) => {
|
|
68
|
+
t.append('tspan').attr('x', 0).attr('dy', '1.1em').style('font-size', 'var(--font-app)').attr('fill', '#888').text(p);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
sim.on('tick', () => {
|
|
73
|
+
linkEl.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
|
|
74
|
+
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
75
|
+
linkLabel
|
|
76
|
+
.attr('x', d => (d.source.x + d.target.x) / 2)
|
|
77
|
+
.attr('y', d => (d.source.y + d.target.y) / 2);
|
|
78
|
+
nodeEl.attr('transform', d => `translate(${d.x},${d.y})`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return () => sim.stop();
|
|
82
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export async function renderGraphviz(content, outputEl) {
|
|
2
|
+
const resp = await fetch('https://kroki.io/graphviz/svg', {
|
|
3
|
+
method: 'POST',
|
|
4
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5
|
+
body: JSON.stringify({ diagram_source: content }),
|
|
6
|
+
});
|
|
7
|
+
if (!resp.ok) throw new Error(`Graphviz render failed: ${resp.statusText}`);
|
|
8
|
+
const svg = await resp.text();
|
|
9
|
+
outputEl.innerHTML = svg;
|
|
10
|
+
// Ensure SVG fills the container
|
|
11
|
+
const svgEl = outputEl.querySelector('svg');
|
|
12
|
+
if (svgEl) { svgEl.style.maxWidth = '100%'; svgEl.style.height = 'auto'; }
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function renderHtml(content, outputEl) {
|
|
2
|
+
outputEl.innerHTML = '';
|
|
3
|
+
const iframe = document.createElement('iframe');
|
|
4
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
5
|
+
iframe.setAttribute('title', 'HTML preview');
|
|
6
|
+
iframe.style.cssText = 'width:100%;height:100%;border:0;display:block';
|
|
7
|
+
outputEl.appendChild(iframe);
|
|
8
|
+
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
|
9
|
+
doc.open(); doc.write(content); doc.close();
|
|
10
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { drawForceGraph } from './d3-force.js';
|
|
2
|
+
|
|
3
|
+
const LABEL_KEYS = new Set([
|
|
4
|
+
'http://xmlns.com/foaf/0.1/name', 'https://schema.org/name',
|
|
5
|
+
'http://www.w3.org/2000/01/rdf-schema#label',
|
|
6
|
+
'http://purl.org/dc/elements/1.1/title',
|
|
7
|
+
'name', 'label', 'title',
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export async function renderJsonLd(content, outputEl) {
|
|
11
|
+
let data;
|
|
12
|
+
try { data = JSON.parse(content); } catch (e) { throw new Error(`JSON parse error: ${e.message}`); }
|
|
13
|
+
|
|
14
|
+
const resources = data['@graph'] || (data['@id'] ? [data] : [data]);
|
|
15
|
+
const nodes = new Map();
|
|
16
|
+
const links = [];
|
|
17
|
+
const shortKey = k => k.split(/[/#:]/).pop() || k;
|
|
18
|
+
|
|
19
|
+
// All declared subject IRIs — used to detect plain-string IRI references
|
|
20
|
+
// (valid in JSON-LD when @type:@id is set in @context)
|
|
21
|
+
const allIds = new Set(resources.map(r => r['@id']).filter(Boolean));
|
|
22
|
+
|
|
23
|
+
resources.forEach(res => {
|
|
24
|
+
const id = res['@id'] || ('_:' + Math.random().toString(36).slice(2));
|
|
25
|
+
if (!nodes.has(id)) nodes.set(id, { id, label: shortKey(id) || 'blank', displayLabel: null, properties: [] });
|
|
26
|
+
const node = nodes.get(id);
|
|
27
|
+
|
|
28
|
+
Object.entries(res).forEach(([key, val]) => {
|
|
29
|
+
if (key.startsWith('@')) return;
|
|
30
|
+
const isLabel = LABEL_KEYS.has(key);
|
|
31
|
+
const vals = Array.isArray(val) ? val : [val];
|
|
32
|
+
|
|
33
|
+
vals.forEach(item => {
|
|
34
|
+
if (typeof item === 'object' && item !== null && item['@id']) {
|
|
35
|
+
const tid = item['@id'];
|
|
36
|
+
if (!nodes.has(tid)) nodes.set(tid, { id: tid, label: shortKey(tid), displayLabel: null, properties: [] });
|
|
37
|
+
links.push({ source: id, target: tid, label: shortKey(key) });
|
|
38
|
+
} else if (typeof item === 'string') {
|
|
39
|
+
if (!isLabel && allIds.has(item)) {
|
|
40
|
+
// String value is an IRI referencing another resource in the graph
|
|
41
|
+
if (!nodes.has(item)) nodes.set(item, { id: item, label: shortKey(item), displayLabel: null, properties: [] });
|
|
42
|
+
links.push({ source: id, target: item, label: shortKey(key) });
|
|
43
|
+
} else if (isLabel && !node.displayLabel) {
|
|
44
|
+
node.displayLabel = item;
|
|
45
|
+
} else {
|
|
46
|
+
node.properties.push(`${shortKey(key)}: ${item}`);
|
|
47
|
+
}
|
|
48
|
+
} else if (typeof item === 'object' && item !== null && item['@value']) {
|
|
49
|
+
const v = String(item['@value']);
|
|
50
|
+
if (isLabel && !node.displayLabel) node.displayLabel = v;
|
|
51
|
+
else node.properties.push(`${shortKey(key)}: ${v}`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (nodes.size === 0) {
|
|
58
|
+
outputEl.innerHTML = '<p style="padding:1rem;color:#888">No resources found. Document needs @graph or @id.</p>';
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await drawForceGraph(outputEl, { nodes: [...nodes.values()], links });
|
|
63
|
+
}
|