aboutsummaryrefslogtreecommitdiffstats
path: root/src/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/index.js')
-rw-r--r--src/index.js309
1 files changed, 73 insertions, 236 deletions
diff --git a/src/index.js b/src/index.js
index 8cad259..a30dbf1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,208 +1,9 @@
import 'preact/debug';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
-import * as jsonld from 'jsonld';
import { h, Fragment, Component, render } from 'preact';
-import { Discussion, Selection } from './ui';
-
-const context = [
- 'https://www.w3.org/ns/activitystreams',
- {
- replies: { '@id': 'as:replies', '@container': '@set' },
- inReplyTo: { '@id': 'as:inReplyTo', '@container': '@set' },
- },
-];
-
-
-const corsPrefix = process.env.CORS_PREFIX || `${location.origin}/remote`;
-console.log(corsPrefix);
-const cors = (url) => `${corsPrefix}/${url}`;
-
-function wrapCache(target) {
- const orig = target.descriptor.value;
-
- target.descriptor.value = function (id, ...args) {
- this.cache[id] ||= orig.call(this, id, ...args);
- return this.cache[id];
- };
-
- return target;
-};
-
-class GraphContainer extends Component {
- state = {
- name: "loading…",
- items: {},
- error: null,
- };
-
- cache = {};
-
- constructor(props) {
- super(props);
-
- if (this.props.graph.startsWith('http'))
- this.loadDataMastodon(this.props.graph)
- .catch(error => {
- console.error(`Error loading Mastodon graph ${this.props.graph}:`, error);
- this.setState({
- name: "error loading",
- error,
- });
- });
- else
- this.loadData(this.props.graph)
- .catch(error => {
- console.error("Error loading default graph:", error);
- this.setState({
- name: "error loading",
- error,
- });
- });
- }
-
- @wrapCache
- async loadCollection(id, ...args) {
- const collection = await jsonld.compact(cors(id), context);
- let items = [];
-
- const addItems = (i) => {
- if (Array.isArray(i))
- items = items.concat(i);
- else if (i)
- items.push(i);
- };
-
- // discover items
- if (collection.items) {
- addItems(collection.items);
- } else {
- let page = collection.first;
- const seen = [];
- while (page && seen.indexOf(page.id) < 0) {
- seen.push(page.id);
- addItems(page.items);
-
- page = page.next && await jsonld.compact(cors(page.next), context);
- }
- }
-
- // dereference items
- return await Promise.all(items.map(item => this.loadNote(item.id ?? item, ...args)));
- }
-
- @wrapCache
- async loadNote(id, items, inReplyTo) {
- let item;
- try {
- item = await jsonld.frame(
- cors(id),
- {
- '@context': context,
- type: 'Note',
- context: { '@embed': '@never' },
- replies: { '@embed': '@always' },
- inReplyTo: { '@embed': '@never' },
- },
- { omitGraph: true }
- );
-
- if (item.replies.length)
- item.replies = await this.loadCollection(item.replies[0].id, items, [id]);
- } catch (error) {
- console.error(`Error loading note '${id}':`, error);
- item = {
- '@context': context,
- id,
- type: 'Tombstone',
- formerType: 'Note',
- attributedTo: 'https://dag.s-ol.nu/users/unknown',
- published: '1970-01-01T00:00:00Z',
- content: `Error loading note: ${error.toString()}`,
- replies: [],
- inReplyTo,
- };
- }
-
- item.attributedTo = await this.loadUser(item.attributedTo);
- items[item.id] = item;
- return item;
- }
-
- async loadDataMastodon(url) {
- const items = {};
- const root = await this.loadNote(url, items, []);
-
- this.setState({
- name: root.name || root.content,
- items,
- });
- }
-
- async loadData(url) {
- const response = await fetch(url, {
- headers: { 'Accept': 'application/json' },
- });
- const discussion = await jsonld.frame(
- await response.json(),
- {
- '@context': context,
- type: 'Document',
- first: { '@embed': '@never' },
- items: {
- context: { '@embed': '@never' },
- replies: { '@embed': '@never' },
- inReplyTo: { '@embed': '@never' },
- attributedTo: { '@embed': '@always' },
- },
- },
- { omitGraph: true }
- );
-
- const indexedItems = {};
- await Promise.all(discussion.items.map(async (item) => {
- item.attributedTo = await this.loadUser(item.attributedTo);
- indexedItems[item.id] = item;
- }));
-
- discussion.items = indexedItems;
-
- this.setState(discussion);
- }
-
- @wrapCache
- async loadUser(id) {
- if (id === "https://dag.s-ol.nu/users/unknown") {
- return {
- '@context': context,
- id,
- type: 'Person',
- name: 'unknown',
- summary: 'unknown user',
- };
- }
-
- try {
- const user = await jsonld.compact(id, context);
- user.id = id;
- return user;
- } catch (error) {
- console.error(`Error loading user '${id}':`, error);
- return {
- '@context': context,
- id,
- type: 'Person',
- name: id,
- summary: 'failed to load user information',
- };
- }
- }
-
- render() {
- window.state = this.state;
- return this.props.render(this.state);
- }
-}
+import { GraphContainerJSONLD, GraphContainerMastodon } from './graph';
+import { Menu, Discussion, Selection } from './ui';
class SelectionContainer extends Component {
state = {
@@ -280,46 +81,82 @@ class CollapseContainer extends Component {
}
}
-const search = new URLSearchParams(window.location.search);
-const graph = search.has('graph') ? search.get('graph') : 'lib/graph.json';
-const app = (
- <GraphContainer graph={graph} render={({ name, items, error }) => {
- if (error) {
- return (
- <article>
- <h1>{name}</h1>
+const graphRender = ({ loading, error, name, items }) => {
+ if (loading) {
+ return (
+ <article>
+ <div>loading...</div>
+ </article>
+ );
+ }
+
+ if (error) {
+ return (
+ <article>
+ <h1>error loading</h1>
+ <div>
<p>{error.toString()}</p>
<pre>
<code>
{error.stack}
</code>
</pre>
- </article>
- );
- }
-
- return (
- <SelectionContainer render={({ selection, toggleSelected }) => (
- <>
- <CollapseContainer
- items={items}
- render={({ collapsed, toggleCollapsed }) => (
- <Discussion
- name={name}
- items={items}
- collapsed={collapsed}
- toggleCollapsed={toggleCollapsed}
- toggleSelected={toggleSelected}
- />
- )}
- />
- <Selection
- items={selection.map(id => items[id])}
- toggleSelected={toggleSelected}
- />
- </>
- )} />
+ </div>
+ </article>
);
- }} />
-);
+ }
+
+ return (
+ <SelectionContainer render={({ selection, toggleSelected }) => (
+ <>
+ <CollapseContainer
+ items={items}
+ render={({ collapsed, toggleCollapsed }) => (
+ <Discussion
+ name={name}
+ items={items}
+ collapsed={collapsed}
+ toggleCollapsed={toggleCollapsed}
+ toggleSelected={toggleSelected}
+ />
+ )}
+ />
+ <Selection
+ items={selection.map(id => items[id])}
+ toggleSelected={toggleSelected}
+ />
+ </>
+ )} />
+ );
+};
+
+const search = new URLSearchParams(window.location.search);
+const graph = search.has('graph') ? search.get('graph') : 'lib/graph.json';
+
+let app;
+if (search.has('document')) {
+ app = (
+ <GraphContainerJSONLD
+ url={search.get('document')}
+ render={graphRender}
+ />
+ );
+} else if (search.has('note')) {
+ app = (
+ <GraphContainerMastodon
+ url={search.get('note')}
+ render={graphRender}
+ />
+ );
+} else if (search.has('graph')) {
+ app = (
+ <GraphContainerMastodon
+ url={search.get('graph')}
+ render={graphRender}
+ />
+ );
+} else {
+ app = <Menu />;
+}
+
render(app, document.body);