aboutsummaryrefslogtreecommitdiffstats
path: root/client/src/graph.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--client/src/graph.js132
1 files changed, 97 insertions, 35 deletions
diff --git a/client/src/graph.js b/client/src/graph.js
index 7fe842c..1b27ea1 100644
--- a/client/src/graph.js
+++ b/client/src/graph.js
@@ -1,5 +1,7 @@
-import { Component } from 'preact';
+import { h } from 'preact';
+import { useMemo, useEffect } from 'preact/hooks';
import * as jsonld from 'jsonld';
+import { useDispatcher, fetchJSON } from './actions';
import { CORS_PREFIX } from './config';
const cors = (url) => `${CORS_PREFIX}/${url}`;
@@ -20,36 +22,41 @@ const context = [
{
replies: { '@id': 'as:replies', '@container': '@set' },
inReplyTo: { '@id': 'as:inReplyTo', '@container': '@set' },
- items: { '@id': 'as:items', '@type': '@id', '@container': '@set' },
+ items: { '@id': 'as:items', '@container': '@set' },
},
];
-export class GraphContainer extends Component {
- state = {
- loading: true,
- name: "loading…",
- items: {},
- error: null,
- };
+export const GraphContainer = ({ url, mode, children }) => {
+ const api = useMemo(() => {
+ const API = mode === "mastodon" ? GraphAPIMastodon : GraphAPIJSONLD;
+ return new API(url);
+ }, [url, mode]);
- cache = {};
+ const { state, dispatch, DispatchProvider } = useDispatcher({
+ reducer: api.reducer,
+ initialState: {
+ loading: true,
+ name: "loading…",
+ items: {},
+ error: null,
+ },
+ });
- componentDidMount() {
- this.componentDidUpdate({});
- }
+ useEffect(() => dispatch({ type: 'reload' }), []);
+
+ return (
+ <DispatchProvider>
+ {children(state)}
+ </DispatchProvider>
+ );
+};
- componentDidUpdate(prevProps) {
- if (prevProps.url === this.props.url) return;
+class GraphAPI {
+ cache = {};
- this.loadData(this.props.url)
- .catch(error => {
- console.error(`Error loading ${this.props.url}:`, error);
- this.setState({
- loading: false,
- error,
- });
- });
- }
+ constructor(url) {
+ this.url = url;
+ }
@wrapCache
async loadUser(id) {
@@ -79,13 +86,21 @@ export class GraphContainer extends Component {
}
}
- render() {
- window.state = this.state;
- return this.props.render(this.state);
+ reducer = (action) => {
+ switch (action.type) {
+ case 'reload':
+ return this.loadData();
+
+ case 'reply':
+ return this.reply(action);
+
+ default:
+ return undefined;
+ }
}
}
-export class GraphContainerMastodon extends GraphContainer {
+export class GraphAPIMastodon extends GraphAPI {
@wrapCache
async loadCollection(id, ...args) {
const collection = await jsonld.compact(cors(id), context);
@@ -154,11 +169,11 @@ export class GraphContainerMastodon extends GraphContainer {
return item;
}
- async loadData(url) {
+ async loadData() {
const items = {};
- const root = await this.loadNote(url, items, []);
+ const root = await this.loadNote(this.url, items, []);
- this.setState({
+ return () => ({
name: root.name || root.content,
items,
loading: false,
@@ -166,9 +181,9 @@ export class GraphContainerMastodon extends GraphContainer {
}
}
-export class GraphContainerJSONLD extends GraphContainer {
- async loadData(url) {
- const response = await fetch(url, {
+class GraphAPIJSONLD extends GraphAPI {
+ async loadData() {
+ const response = await fetch(this.url, {
headers: { 'Accept': 'application/ld+json, application/json' },
credentials: 'include',
});
@@ -197,9 +212,56 @@ export class GraphContainerJSONLD extends GraphContainer {
discussion.items = indexedItems;
- this.setState({
+ return () => ({
...discussion,
loading: false,
});
}
+
+ async reply({ content, to }) {
+ const result = await fetchJSON(this.url, 'POST', {
+ type: 'Create',
+ object: {
+ type: 'Note',
+ inReplyTo: to,
+ content,
+ },
+ });
+
+ const changed = await jsonld.frame(
+ result,
+ {
+ '@context': context,
+ items: {
+ context: { '@embed': '@never' },
+ replies: { '@embed': '@never' },
+ inReplyTo: { '@embed': '@never' },
+ attributedTo: { '@embed': '@always' },
+ },
+ },
+ { omitGraph: true }
+ );
+
+ await Promise.all(changed.items.map(async (item) => {
+ item.attributedTo = await this.loadUser(item.attributedTo);
+ }));
+
+ return (state) => {
+ const items = {...state.items};
+ for (const note of changed.items) {
+ items[note.id] = note;
+
+ for (const { id } of note.inReplyTo) {
+ if (items[id].replies.indexOf(note.id) > -1) continue;
+
+ items[id] = {
+ ...items[id],
+ replies: [...items[id].replies, { id: note.id }],
+ };
+ }
+ }
+
+ return { ...state, items };
+ }
+ }
}