aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.js3
-rw-r--r--src/graph.js205
-rw-r--r--src/index.js162
-rw-r--r--src/layout.js48
-rw-r--r--src/ui/Attachment.js26
-rw-r--r--src/ui/Discussion.js122
-rw-r--r--src/ui/Links.js50
-rw-r--r--src/ui/Menu.js139
-rw-r--r--src/ui/Note.js249
-rw-r--r--src/ui/Selection.js93
-rw-r--r--src/ui/css.js13
-rw-r--r--src/ui/index.js10
-rw-r--r--src/ui/theme.js39
-rw-r--r--src/viz.es.js194
14 files changed, 0 insertions, 1353 deletions
diff --git a/src/config.js b/src/config.js
deleted file mode 100644
index 91d6231..0000000
--- a/src/config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const PUBLIC_URL = process.env.PUBLIC_URL || `${location.origin}`;
-export const API_PREFIX = process.env.API_PREFIX || `${location.origin}`;
-export const CORS_PREFIX = process.env.CORS_PREFIX || `${API_PREFIX}/remote`;
diff --git a/src/graph.js b/src/graph.js
deleted file mode 100644
index 7fe842c..0000000
--- a/src/graph.js
+++ /dev/null
@@ -1,205 +0,0 @@
-import { Component } from 'preact';
-import * as jsonld from 'jsonld';
-import { CORS_PREFIX } from './config';
-
-const cors = (url) => `${CORS_PREFIX}/${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;
-};
-
-const context = [
- 'https://www.w3.org/ns/activitystreams',
- {
- replies: { '@id': 'as:replies', '@container': '@set' },
- inReplyTo: { '@id': 'as:inReplyTo', '@container': '@set' },
- items: { '@id': 'as:items', '@type': '@id', '@container': '@set' },
- },
-];
-
-export class GraphContainer extends Component {
- state = {
- loading: true,
- name: "loading…",
- items: {},
- error: null,
- };
-
- cache = {};
-
- componentDidMount() {
- this.componentDidUpdate({});
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.url === this.props.url) return;
-
- this.loadData(this.props.url)
- .catch(error => {
- console.error(`Error loading ${this.props.url}:`, error);
- this.setState({
- loading: false,
- error,
- });
- });
- }
-
- @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);
- }
-}
-
-export class GraphContainerMastodon extends GraphContainer {
- @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 loadData(url) {
- const items = {};
- const root = await this.loadNote(url, items, []);
-
- this.setState({
- name: root.name || root.content,
- items,
- loading: false,
- });
- }
-}
-
-export class GraphContainerJSONLD extends GraphContainer {
- async loadData(url) {
- const response = await fetch(url, {
- headers: { 'Accept': 'application/ld+json, application/json' },
- credentials: 'include',
- });
-
- 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,
- loading: false,
- });
- }
-}
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index a30dbf1..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import 'preact/debug';
-import 'core-js/stable';
-import 'regenerator-runtime/runtime';
-import { h, Fragment, Component, render } from 'preact';
-import { GraphContainerJSONLD, GraphContainerMastodon } from './graph';
-import { Menu, Discussion, Selection } from './ui';
-
-class SelectionContainer extends Component {
- state = {
- selection: [],
- };
-
- toggle = (id) => {
- const { selection } = this.state;
- if (selection.indexOf(id) < 0)
- this.setState({ selection: [ ...selection, id ] });
- else
- this.setState({ selection: selection.filter(i => i !== id) });
- }
-
- render() {
- return this.props.render({
- ...this.state,
- toggleSelected: this.toggle,
- });
- }
-}
-
-class CollapseContainer extends Component {
- /**
- * Contains the collapse-state for each node.
- *
- * - `false`: visible
- * - `true`: implicitly hidden
- * - `'explicit'`: explicitly hidden
- */
- state = {};
-
- /**
- * Toggle a node's collapse-state.
- * - `false` → `'explicit'`
- * - `true`, `'explicit'` → `false`
- */
- toggle = (id) => {
- const { items } = this.props;
-
- const state = Object.assign({}, this.state);
- state[id] = state[id] ? false : 'explicit';
-
- this.setState(this.update(items[id], state));
- }
-
- /**
- * Recursively update child node's collapse-state.
- *
- * If a node has parents and they are all collapsed,
- * mark that node as implicitly hidden (`true`).
- *
- * Then update all of that node's children.
- */
- update(node, state) {
- const { items } = this.props;
-
- if (state[node.id] !== 'explicit') {
- state[node.id] =
- node.inReplyTo.length
- && node.inReplyTo.every(p => state[p.id]);
- }
-
- for (const child of node.replies)
- this.update(items[child.id], state);
-
- return state;
- }
-
- render() {
- return this.props.render({
- collapsed: this.state,
- toggleCollapsed: this.toggle,
- });
- }
-}
-
-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>
- </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);
diff --git a/src/layout.js b/src/layout.js
deleted file mode 100644
index c9c2a4d..0000000
--- a/src/layout.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import Viz from './viz.es';
-
-const toNumber = (i) => Number(i);
-const gv = new Viz({ workerURL: 'lib/lite.render.js' });
-
-export default class Layout {
- constructor() {
- this.src = 'digraph { node [shape="rectangle"];\n';
- }
-
- addNode(id, width, height) {
- this.src += ` "${id}" [fixedsize=true; width=${width / 72}; height=${height / 72}];\n`;
- }
-
- addLink(from, to) {
- this.src += ` "${from}" -> "${to}";\n`;
- }
-
- async render() {
- 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 = {};
- for (const object of info.objects) {
- const [x, y] = object.pos.split(',').map(toNumber);
- positions[object.name] = [ x, height - y ];
- }
-
- const links = info.edges.map((edge) => {
- const pos = edge.pos.slice(2);
- return pos.split(' ').map(p => {
- const [x, y] = p.split(',');
- return [toNumber(x), height - toNumber(y)];
- });
-
- });
-
- return {
- width, height,
- positions,
- links,
- };
- }
-}
diff --git a/src/ui/Attachment.js b/src/ui/Attachment.js
deleted file mode 100644
index 3c07ee5..0000000
--- a/src/ui/Attachment.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { h } from 'preact';
-import css from './css';
-
-css`
-.attachment {
- margin: 1rem;
-
- height: 7rem;
- object-fit: contain;
-}
-
-.attachment + .attachment {
- margin-top: 0;
-}
-`;
-
-export const Attachment = ({ type, mediaType, url, style }) => {
- let content;
- if (mediaType.startsWith('video/')) {
- content = <video class="attachment" src={url} controls />;
- } else if (mediaType.startsWith('image/')) {
- content = <img class="attachment" src={url} />;
- }
-
- return content;
-}
diff --git a/src/ui/Discussion.js b/src/ui/Discussion.js
deleted file mode 100644
index 5fef693..0000000
--- a/src/ui/Discussion.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import { h } from 'preact';
-import { useState, useEffect } from 'preact/hooks';
-import Layout from '../layout';
-import css from './css';
-import { Links } from './Links';
-import { Note } from './Note';
-
-css`
-article {
- position: absolute;
- inset: 0;
-
- overflow: auto;
-}
-
-article > h1 {
- display: flex;
- align-items: baseline;
-
- position: sticky;
- inset: 0;
- bottom: auto;
- z-index: 200;
-
- margin: 0;
- padding: 0.75rem 1.5rem;
- background: var(--theme-title-bg);
- color: var(--theme-title-fg);
-}
-
-article > h1 > span {
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
-
-article > h1 .back {
- font-size: 0.8em;
- color: inherit;
- opacity: 0.5;
-
- margin-left: 1em;
-}
-
-article > div {
- position: relative;
- margin: 2rem 2rem 6rem;
-}
-`;
-
-const tmp = document.createElement('span');
-
-export const Discussion = ({
- name, items, collapsed,
- toggleCollapsed, toggleSelected,
-}) => {
- const [{ width, height, positions, links }, setState] = useState({
- width: 0,
- height: 0,
- positions: {},
- links: [],
- });
-
- const layout = new Layout();
- const setNodeSize = layout.addNode.bind(layout);
-
- useEffect(() => {
- layout.render().then(setState);
- }, [ items, collapsed ]);
-
- tmp.innerHTML = name;
- name = tmp.innerText;
-
- return (
- <article>
- <h1>
- <span>{name}</span>
- <a class="back" href="?">back</a>
- </h1>
- <div>
- <Links width={width} height={height} links={links} />
- <div>
- {Object.values(items).map((item) => {
- const isCollapsed = collapsed[item.id];
- const hidden =
- item.inReplyTo.every(p => collapsed[p.id])
- && item.replies.every(c => collapsed[c.id])
- && item.inReplyTo.length !== 0;
- if (hidden)
- return;
-
- const onlyParent = item.inReplyTo.length === 1 && items[item.inReplyTo[0].id];
-
- for (const reply of item.replies) {
- if (isCollapsed && collapsed[reply.id])
- continue;
-
- layout.addLink(item.id, reply.id);
- }
-
- return (
- <Note
- {...item}
- smallHeader={
- onlyParent // only one parent post
- && onlyParent.attributedTo === item.attributedTo // posted by same user
- && onlyParent.published === item.published // in the same second
- }
- collapsed={isCollapsed}
- position={positions[item.id]}
- onCollapse={toggleCollapsed}
- onSelect={toggleSelected}
- onSize={setNodeSize}
- />
- );
- })}
- </div>
- </div>
- </article>
- );
-};
-
diff --git a/src/ui/Links.js b/src/ui/Links.js
deleted file mode 100644
index 0a0b1ef..0000000
--- a/src/ui/Links.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { h } from 'preact';
-import { useRef, useLayoutEffect } from 'preact/hooks';
-
-export const Links = ({ links, ...props }) => {
- const canvas = useRef(null);
-
- useLayoutEffect(() => {
- const ctx = canvas.current.getContext('2d');
- ctx.lineWidth = 4;
- ctx.lineCap = 'round';
- ctx.lineJoin = 'round';
- ctx.strokeStyle = '#696969';
- ctx.fillStyle = '#696969';
- ctx.shadowBlur = 2;
- ctx.shadowOffsetX = 2;
- ctx.shadowOffsetY = 4;
-
- ctx.clearRect(0, 0, canvas.current.width, canvas.current.height);
-
- for (const points of links) {
- ctx.shadowColor = 'rgba(0,0,0, 0.7)';
- ctx.beginPath();
- ctx.moveTo(...points[1]);
- for (let i = 4; i < points.length; i += 3) {
- const [ax, ay] = points[i - 2];
- const [bx, by] = points[i - 1];
- const [x, y] = points[i];
- ctx.bezierCurveTo(ax, ay, bx, by, x, y);
- }
- ctx.stroke();
-
- const [lx, ly] = points[points.length - 1];
- const [tx, ty] = points[0];
- const [dx, dy] = [tx - lx, ty - ly];
- const [hx, hy] = [dy*0.7, dx*-0.7];
-
- ctx.shadowColor = 'transparent';
- ctx.beginPath();
- ctx.moveTo(lx + dx*0.8, ly + dy*0.8);
- ctx.lineTo(lx - dx*0.5 + hx, ly - dy*0.5 + hy);
- ctx.lineTo(lx - dx*0.5 - hx, ly - dy*0.5 - hy);
- ctx.fill();
- }
- });
-
- return (
- <canvas {...props} ref={canvas} />
- );
-};
-
diff --git a/src/ui/Menu.js b/src/ui/Menu.js
deleted file mode 100644
index 6f32280..0000000
--- a/src/ui/Menu.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import { h, Fragment } from 'preact';
-import { useState, useEffect } from 'preact/hooks';
-import cn from 'classnames';
-import { API_PREFIX } from '../config';
-import css from './css';
-
-const fetchJSON = async (url) => {
- const res = await fetch(API_PREFIX + url, { credentials: 'include' });
- if (res.status !== 200)
- throw new Error("wrong status");
-
- return res.json();
-};
-
-css`
-a.discussion {
- display: flex;
-
- padding: 0.25rem 0.5rem;
- text-size: 2rem;
- line-height: 2rem;
- text-decoration: none;
-
- border-radius: 0.3rem;
-
- color: var(--theme-note-fg);
- background: var(--theme-note-bg);
-}
-
-a.discussion .access {
- opacity: 0.5;
- margin-right: 0.75rem;
-}
-
-a.discussion .name {
- flex: 1 1 auto;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-a.discussion > a {
- margin-left: 0.75rem;
- text-decoration: underline;
- color: inherit;
- opacity: 0.5;
-
- transition: opacity 0.3s;
-}
-a.discussion > a:hover {
- opacity: 1;
-}
-`;
-
-const DiscussionLink = ({ id, name, url, attributedTo, user }) => {
- const writable = attributedTo && attributedTo.indexOf(user) > -1;
-
- return (
- <a class="discussion" href={`?document=${id}`}>
- <span class="access">{writable ? 'W' : 'R'}</span>
- <span class="name">{name}</span>
- {url && (
- <a href={url.href}>ext</a>
- )}
- </a>
- );
-};
-
-css`
-ul.discussions {
- list-style: none;
- padding: 0;
-
- width: 26rem;
-}
-
-ul.discussions > li {
- margin: 0.2rem 0;
-}
-`;
-
-const DiscussionList = ({ discussions, user }) => {
- return (
- <ul class="discussions">
- {discussions.map((discussion) => (
- <li>
- <DiscussionLink {...discussion} user={user} />
- </li>
- ))}
- </ul>
- );
-};
-
-css`
-div.menu {
- margin: 1rem 2rem;
-}
-`;
-export const Menu = () => {
- const [user, setUser] = useState(null);
- const [discussions, setDiscussions] = useState([]);
-
- useEffect(() => {
- fetchJSON('/discdag/list')
- .then(({ user, discussions }) => {
- setUser(user ?? null);
- setDiscussions(discussions);
- });
- }, []);
-
- if (user === null) {
- return (
- <div class="menu">
- <h2>Discussions</h2>
- loading...
- </div>
- );
- }
-
- return (
- <div class="menu">
- <h2>Discussions</h2>
- {/*user && user.name !== 'Guest'
- ? (
- <>
- <span>logged in as {user.name}</span>
- <button>log out</button>
- </>
- )
- : (
- <>
- <span>not logged in</span>
- <button>log in</button>
- </>
- )*/}
- <DiscussionList discussions={discussions} user={user?.id} />
- </div>
- );
-};
diff --git a/src/ui/Note.js b/src/ui/Note.js
deleted file mode 100644
index 16b31cb..0000000
--- a/src/ui/Note.js
+++ /dev/null
@@ -1,249 +0,0 @@
-import { h } from 'preact';
-import { useRef, useLayoutEffect } from 'preact/hooks';
-import ColorHash from 'color-hash';
-import { formatRelative } from 'date-fns';
-import cn from 'classnames';
-import css from './css';
-import { Attachment } from './Attachment';
-
-const now = new Date();
-const Time = ({ time }) => {
- const dt = new Date(time);
- return formatRelative(dt, now);
-};
-
-css`
-.flex-space {
- flex: 1;
- margin: 0;
-}
-`;
-const Space = (props) => <div class="flex-space" {...props} />;
-
-css`
-section > header {
- display: flex;
- flex-direction: row;
-
- height: 1.75rem;
-
- color: var(--theme-header-fg);
- background: var(--theme-header-bg);
-}
-
-section > header.small {
- height: 0.5rem;
-}
-
-section > header > * {
- margin: 0 0.35rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 1.75rem;
-}
-
-section > header > .avatar {
- height: 1.25rem;
- width: 1.25rem;
- flex: 0 0 auto;
- margin: 0.25rem;
- border-radius: 0.2rem;
-
- font-size: 1.25rem;
- font-weight: bold;
- line-height: 1.25rem;
- text-align: center;
- text-decoration: none;
- background: white;
- color: black;
-}
-section > header > .avatar img {
- width: inherit;
- height: inherit;
-}
-section > header > .collapse {
- height: 1rem;
- width: 1rem;
- flex: 0 0 auto;
- margin: 0.3rem;
- border-radius: 0.2rem;
- border: 0.05rem solid var(--theme-header-fg);
-
- line-height: 0.8rem;
- text-align: center;
- text-decoration: none;
- text-weight: bold;
-
- transition: background 0.3s;
-}
-section > header > .collapse:hover {
- background: var(--theme-note-bg);
-}
-
-section > header > .user {
- flex: 0 0.5 auto;
-}
-
-section > header > .time {
- flex: 0 0 auto;
- font-size: 0.8em;
- color: #363636;
-}
-`;
-const Header = ({ id, user, published, collapsed, onCollapse }) => {
- const username = user.name || user.preferredUsername;
-
- const onClick = onCollapse && ((e) => {
- e.preventDefault();
- onCollapse(id);
- });
-
- return (
- <header>
- <a class="avatar" href={user.id}>
- {user.icon
- ? <img src={new URL(user.icon.url, user.id)} />
- : "?"}
- </a>
- <span class="user">{username}</span>
- <Space />
- <a class="time" href={id}>
- <Time time={published} />
- </a>
- <a class="collapse" href="#" onClick={onClick}>
- {collapsed ? '+' : '-'}
- </a>
- </header>
- );
-};
-
-css`
-section {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-
- width: 15rem;
-
- overflow: hidden;
- color: var(--theme-note-fg);
- background: var(--theme-note-bg);
- box-shadow: rgba(0,0,0, 0.7) 2px 4px 5px;
-
- border-radius: 0.3rem;
-}
-
-section.type-Tombstone {
- color: var(--theme-note-bg);
- background: var(--theme-note-fg);
-}
-
-section > main {
- position: relative;
- padding: 0.5rem;
- overflow: hidden;
- 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;
-}
-
-section.hidden {
- display: none;
-}
-
-section.ellipsis > main {
- max-height: 7em;
-}
-section.ellipsis > .attachment {
- display: none;
-}
-section.ellipsis > main::after {
- position: absolute;
- display: block;
- content: '';
-
- pointer-events: none;
-
- left: 0;
- right: 0;
- bottom: 0;
- height: 1.25em;
- background: linear-gradient(0deg, var(--theme-note-bg) 5%, var(--theme-note-bg-trans) 100%);
-}
-section.type-Tombstone.ellipsis > main::after {
- background: linear-gradient(0deg, var(--theme-note-fg) 5%, var(--theme-note-fg-trans) 100%);
-}
-
-section.collapsed > main {
- max-height: 2em;
-}
-`;
-
-const colors = new ColorHash({ lightness: 0.8 });
-export const Note = ({
- id, type, attributedTo, published, content, attachment,
- smallHeader = false, collapsed = false, ellipsis = false, position,
- onCollapse, onSelect, onSize,
-}) => {
- ellipsis ||= collapsed;
- attachment ||= [];
- if (!Array.isArray(attachment))
- attachment = [attachment];
-
- const backgroundColor = attributedTo.color || colors.hex(attributedTo.id);
-
- const onClick = onSelect && ((e) => {
- e.preventDefault();
- onSelect(id);
- });
-
- const root = useRef(null);
- onSize && useLayoutEffect(() => {
- onSize(id, root.current.offsetWidth, root.current.offsetHeight);
- });
-
- return (
- <section
- style={{
- '--theme-header-bg': backgroundColor,
- 'position': position && 'absolute',
- 'left': position && `${position[0] - root.current.offsetWidth / 2}px`,
- 'top': position && `${position[1] - root.current.offsetHeight / 2}px`,
- 'visibility': onSize && !position ? 'hidden' : 'visible',
- }}
- class={cn(
- `type-${type}`,
- collapsed && 'collapsed',
- ellipsis && 'ellipsis',
- )}
- data-id={id}
- ref={root}
- >
- {smallHeader
- ? <header className="small" onClick={onCollapse} />
- : <Header
- id={id}
- user={attributedTo}
- published={published}
- collapsed={collapsed}
- onCollapse={onCollapse}
- />
- }
- <main
- innerHTML={content}
- onClick={onClick}
- />
- {attachment.map(a => <Attachment {...a} />)}
- </section>
- );
-};
diff --git a/src/ui/Selection.js b/src/ui/Selection.js
deleted file mode 100644
index d8dfa88..0000000
--- a/src/ui/Selection.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import { h } from 'preact';
-import { useState, useEffect } from 'preact/hooks';
-import cn from 'classnames';
-import css from './css';
-import { Note } from './Note';
-
-css`
-.drawer {
- position: fixed;
- inset: 0;
- top: auto;
-
- z-index: 200;
- background: var(--theme-note-fg);
-
- /* wtf webkit */
- -webkit-backface-visibility: hidden;
-}
-
-.drawer .handle {
- height: 1.5rem;
- margin-bottom: 1rem;
-}
-
-.drawer .handle button {
- display: block;
-
- height: 1rem;
- line-height: 1rem;
- margin: -1rem auto 0;
- padding: 0.25rem;
- background: var(--theme-note-bg);
- border-radius: 0.2rem;
-}
-
-.drawer .scroller {
- display: flex;
- overflow: auto;
- width: 100%;
-
- transition: max-height 0.3s;
-}
-
-.drawer .contents {
- display: flex;
-
- align-items: flex-start;
-
- gap: 1rem;
- padding: 0 1.5rem;
-}
-
-.drawer .contents > * {
- flex: 0 0 auto;
-}
-`;
-
-export const Drawer = ({ height, children }) => {
- const [ hidden, setHidden ] = useState(true);
-
- return (
- <div class="drawer">
- <div class="handle">
- <button onClick={() => setHidden(!hidden)}>
- ###
- </button>
- </div>
- <div
- class="scroller"
- style={{
- height,
- 'max-height': hidden ? '1rem' : height,
- }}
- >
- <div class="contents">
- {children}
- </div>
- </div>
- </div>
- );
-};
-
-export const Selection = ({ items, toggleSelected }) => (
- <Drawer height="8.55rem" >
- {items.map((item) => (
- <Note
- {...item}
- ellipsis
- onSelect={toggleSelected}
- />
- ))}
- </Drawer>
-);
diff --git a/src/ui/css.js b/src/ui/css.js
deleted file mode 100644
index 9089f1e..0000000
--- a/src/ui/css.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default ([ code ]) => {
- const style = document.createElement('style');
- document.head.appendChild(style);
- style.appendChild(document.createTextNode(''));
-
- let i = 0;
- for (const v of code.split('}')) {
- if (v.indexOf('{') < 0) continue;
- style.sheet.insertRule(v + '}', i++);
- }
-
- return style;
-};
diff --git a/src/ui/index.js b/src/ui/index.js
deleted file mode 100644
index f9ebf7f..0000000
--- a/src/ui/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import './theme';
-import { Discussion } from './Discussion';
-import { Selection } from './Selection';
-import { Menu } from './Menu';
-
-export {
- Discussion,
- Selection,
- Menu,
-};
diff --git a/src/ui/theme.js b/src/ui/theme.js
deleted file mode 100644
index 8977791..0000000
--- a/src/ui/theme.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import css from './css';
-
-css`
-html {
- margin: 0;
- padding: 0;
-}
-
-body {
- --theme-backdrop: #1e1e1e;
- --theme-fg: #eeeeee;
- --theme-note-bg: #eeeeee;
- --theme-note-fg: #363636;
- --theme-note-bg-trans: rgba(238,238,238,0);
- --theme-note-fg-trans: rgba(54,54,54,0);
- --theme-header-fg: #121212;
-
- --theme-title-fg: #eeeeee;
- --theme-title-bg: #363636;
-}
-
-body.darktheme {
- --theme-backdrop: #1e1e1e;
- --theme-note-bg: #363636;
- --theme-note-fg: #eeeeee;
- --theme-note-bg-trans: rgba(54,54,54,0);
- --theme-note-fg-trans: rgba(238,238,238,0);
- --theme-header-fg: #121212;
-}
-
-body {
- color: var(--theme-fg);
- background: var(--theme-backdrop);
- font-family: sans-serif;
-
- margin: 0;
- padding: 0;
-}
-`;
diff --git a/src/viz.es.js b/src/viz.es.js
deleted file mode 100644
index 56f6a98..0000000
--- a/src/viz.es.js
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
-Viz.js 2.1.2 (Graphviz 2.40.1, Expat {{EXPAT_VERSION}}, Emscripten 1.37.36)
-Copyright (c) 2014-2018 Michael Daines
-Licensed under MIT license
-
-This distribution contains other software in object code form:
-
-Graphviz
-Licensed under Eclipse Public License - v 1.0
-http://www.graphviz.org
-
-Expat
-Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper
-Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers.
-Licensed under MIT license
-http://www.libexpat.org
-
-zlib
-Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
-http://www.zlib.net/zlib_license.html
-*/
-var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
- return typeof obj;
-} : function (obj) {
- return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
-};
-
-var classCallCheck = function (instance, Constructor) {
- if (!(instance instanceof Constructor)) {
- throw new TypeError("Cannot call a class as a function");
- }
-};
-
-var createClass = function () {
- function defineProperties(target, props) {
- for (var i = 0; i < props.length; i++) {
- var descriptor = props[i];
- descriptor.enumerable = descriptor.enumerable || false;
- descriptor.configurable = true;
- if ("value" in descriptor) descriptor.writable = true;
- Object.defineProperty(target, descriptor.key, descriptor);
- }
- }
-
- return function (Constructor, protoProps, staticProps) {
- if (protoProps) defineProperties(Constructor.prototype, protoProps);
- if (staticProps) defineProperties(Constructor, staticProps);
- return Constructor;
- };
-}();
-
-var _extends = Object.assign || function (target) {
- for (var i = 1; i < arguments.length; i++) {
- var source = arguments[i];
-
- for (var key in source) {
- if (Object.prototype.hasOwnProperty.call(source, key)) {
- target[key] = source[key];
- }
- }
- }
-
- return target;
-};
-
-var WorkerWrapper = function () {
- function WorkerWrapper(worker) {
- var _this = this;
-
- classCallCheck(this, WorkerWrapper);
-
- this.worker = worker;
- this.listeners = [];
- this.nextId = 0;
-
- this.worker.addEventListener('message', function (event) {
- var id = event.data.id;
- var error = event.data.error;
- var result = event.data.result;
-
- _this.listeners[id](error, result);
- delete _this.listeners[id];
- });
- }
-
- createClass(WorkerWrapper, [{
- key: 'render',
- value: function render(src, options) {
- var _this2 = this;
-
- return new Promise(function (resolve, reject) {
- var id = _this2.nextId++;
-
- _this2.listeners[id] = function (error, result) {
- if (error) {
- reject(new Error(error.message, error.fileName, error.lineNumber));
- return;
- }
- resolve(result);
- };
-
- _this2.worker.postMessage({ id: id, src: src, options: options });
- });
- }
- }]);
- return WorkerWrapper;
-}();
-
-var ModuleWrapper = function ModuleWrapper(module, render) {
- classCallCheck(this, ModuleWrapper);
-
- var instance = module();
- this.render = function (src, options) {
- return new Promise(function (resolve, reject) {
- try {
- resolve(render(instance, src, options));
- } catch (error) {
- reject(error);
- }
- });
- };
-};
-
-// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
-
-var Viz = function () {
- function Viz() {
- var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
- workerURL = _ref3.workerURL,
- worker = _ref3.worker,
- Module = _ref3.Module,
- render = _ref3.render;
-
- classCallCheck(this, Viz);
-
- if (typeof workerURL !== 'undefined') {
- this.wrapper = new WorkerWrapper(new Worker(workerURL));
- } else if (typeof worker !== 'undefined') {
- this.wrapper = new WorkerWrapper(worker);
- } else if (typeof Module !== 'undefined' && typeof render !== 'undefined') {
- this.wrapper = new ModuleWrapper(Module, render);
- } else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') {
- this.wrapper = new ModuleWrapper(Viz.Module, Viz.render);
- } else {
- throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.');
- }
- }
-
- createClass(Viz, [{
- key: 'renderString',
- value: function renderString(src) {
- var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
- _ref4$format = _ref4.format,
- format = _ref4$format === undefined ? 'svg' : _ref4$format,
- _ref4$engine = _ref4.engine,
- engine = _ref4$engine === undefined ? 'dot' : _ref4$engine,
- _ref4$files = _ref4.files,
- files = _ref4$files === undefined ? [] : _ref4$files,
- _ref4$images = _ref4.images,
- images = _ref4$images === undefined ? [] : _ref4$images,
- _ref4$yInvert = _ref4.yInvert,
- yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert,
- _ref4$nop = _ref4.nop,
- nop = _ref4$nop === undefined ? 0 : _ref4$nop;
-
- for (var i = 0; i < images.length; i++) {
- files.push({
- path: images[i].path,
- data: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg width="' + images[i].width + '" height="' + images[i].height + '"></svg>'
- });
- }
-
- return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert, nop: nop });
- }
- }, {
- key: 'renderJSONObject',
- value: function renderJSONObject(src) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- var format = options.format;
-
-
- if (format !== 'json' || format !== 'json0') {
- format = 'json';
- }
-
- return this.renderString(src, _extends({}, options, { format: format })).then(function (str) {
- return JSON.parse(str);
- });
- }
- }]);
- return Viz;
-}();
-
-export default Viz;