diff options
Diffstat (limited to 'src/index.js')
| -rw-r--r-- | src/index.js | 309 |
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); |
