aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2021-02-06 11:46:13 +0000
committers-ol <s-ol@users.noreply.github.com>2021-02-07 12:06:24 +0000
commit09c3c35c3302f1134cb404a2b7aad2aa7eb3792b (patch)
treec094a4033163f96bb454f38e6ef78ea7d1527ee3 /src
parentfactor out Discussion component (diff)
downloadfedidag-09c3c35c3302f1134cb404a2b7aad2aa7eb3792b.tar.gz
fedidag-09c3c35c3302f1134cb404a2b7aad2aa7eb3792b.zip
Mastodon semi-compatibility
Diffstat (limited to 'src')
-rw-r--r--src/cors-proxy.js12
-rw-r--r--src/index.js105
-rw-r--r--src/layout.js2
-rw-r--r--src/ui/Note.js11
4 files changed, 110 insertions, 20 deletions
diff --git a/src/cors-proxy.js b/src/cors-proxy.js
new file mode 100644
index 0000000..905eb0f
--- /dev/null
+++ b/src/cors-proxy.js
@@ -0,0 +1,12 @@
+var host = process.env.HOST || '127.0.0.1';
+var port = process.env.PORT || 8088;
+
+var cors_proxy = require('cors-anywhere');
+cors_proxy.createServer({
+ originWhitelist: [], // Allow all origins
+ requireHeader: ['origin', 'x-requested-with'],
+ removeHeaders: ['cookie', 'cookie2'],
+ checkRateLimit: null,
+}).listen(port, host, function() {
+ console.log('Running CORS Anywhere on ' + host + ':' + port);
+});
diff --git a/src/index.js b/src/index.js
index b749a53..1169307 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,3 +1,4 @@
+import 'preact/debug';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import * as jsonld from 'jsonld';
@@ -12,26 +13,98 @@ const context = [
},
];
+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: [],
+ items: {},
};
- usersCache = {};
+ cache = {};
constructor(props) {
super(props);
- this.loadData(this.props.graph)
- .catch(err => console.error(err));
+ if (this.props.graph.startsWith('http'))
+ this.loadDataMastodon(this.props.graph)
+ .catch(err => console.error(err));
+ else
+ this.loadData(this.props.graph)
+ .catch(err => console.error(err));
}
- async loadData(url) {
- // temporary hack
- if (url.startsWith('http'))
- url = `https://cors-anywhere.herokuapp.com/${url}`;
+ @wrapCache
+ async loadCollection(id, ...args) {
+ const collection = await jsonld.compact(`http://localhost:8088/${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 (seen.indexOf(page.id) < 0) {
+ seen.push(page.id);
+ addItems(page.items);
+
+ page = await jsonld.compact(`http://localhost:8088/${page.next}`, context);
+ }
+ }
+
+ // dereference items
+ return await Promise.all(items.map(item => this.loadNote(item.id ?? item, ...args)));
+ }
+
+ @wrapCache
+ async loadNote(id, items) {
+ const item = await jsonld.frame(
+ `http://localhost:8088/${id}`,
+ {
+ '@context': context,
+ type: 'Note',
+ context: { '@embed': '@never' },
+ replies: { '@embed': '@always' },
+ inReplyTo: { '@embed': '@never' },
+ },
+ { omitGraph: true }
+ );
+
+ item.attributedTo = await this.loadUser(item.attributedTo);
+ item.replies = await this.loadCollection(item.replies[0].id, items);
+
+ items[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',
@@ -63,18 +136,15 @@ class GraphContainer extends Component {
this.setState(discussion);
}
+ @wrapCache
async loadUser(id) {
- if (!this.usersCache[id]) {
- this.usersCache[id] = jsonld.compact(id, context).then((user) => {
- user.id = id;
- return user;
- });
- }
-
- return await this.usersCache[id];
+ const user = await jsonld.compact(id, context);
+ user.id = id;
+ return user;
}
render() {
+ window.s = this.state;
return this.props.render(this.state);
}
}
@@ -114,7 +184,8 @@ class CollapseContainer extends Component {
}
}
-const graph = document.location.hash ? document.location.hash.substr(1) : 'lib/graph.json';
+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 }) => (
<CollapseContainer
diff --git a/src/layout.js b/src/layout.js
index 000d22c..c9c2a4d 100644
--- a/src/layout.js
+++ b/src/layout.js
@@ -20,6 +20,8 @@ export default class Layout {
this.src += '}';
const info = await gv.renderJSONObject(this.src, { format: 'json0' });
+ info.objects ||= [];
+ info.edges ||= [];
const [width, height] = info.bb.split(',').map(toNumber).slice(2);
const positions = {};
diff --git a/src/ui/Note.js b/src/ui/Note.js
index 34c15d7..6bdec04 100644
--- a/src/ui/Note.js
+++ b/src/ui/Note.js
@@ -103,6 +103,13 @@ section > main {
font-family: serif;
}
+section > main > p:first-child {
+ margin-top: 0;
+}
+section > main > p:last-child {
+ margin-bottom: 0;
+}
+
section.tombstone > main {
font-family: inherit;
}
@@ -153,9 +160,7 @@ export const Note = ({
? <header className="small" onClick={onClickHeader} />
: <Header onClick={onClickHeader} user={attributedTo} published={published} />
}
- <main>
- {content}
- </main>
+ <main innerHTML={content} />
</section>
);
};