mirror of
https://github.com/GenderDysphoria/GenderDysphoria.fyi.git
synced 2025-11-25 20:42:40 +00:00
New preact based frontend UI for the TTT.
Added rollup compiling
This commit is contained in:
132
js/$tweets.jsx
Normal file
132
js/$tweets.jsx
Normal file
@@ -0,0 +1,132 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { h, render, Component, Fragment } from 'preact';
|
||||
import map from 'lodash/map';
|
||||
// import memoize from 'lodash/memoize';
|
||||
import { format } from 'date-fns';
|
||||
import Sync from 'svg/sync-alt.svg';
|
||||
import Link from 'svg/link.svg';
|
||||
|
||||
// const If = ({t,children}) => (!!t && <Fragment>{children}</Fragment>)
|
||||
|
||||
const Raw = ({ html }) => <div dangerouslySetInnerHTML={{ __html: html }} />;
|
||||
|
||||
const Post = ({ post }) => (
|
||||
<article>
|
||||
<div class="post-head">
|
||||
<div class="post-tags">
|
||||
{map(post.tags, (v, k) => <a href={'/tweets/#tag=' + k} class="post-link tag">{v}</a>)}
|
||||
{map(post.author, (v) => <a href={'/tweets/#author=' + v} class="post-link author">{v}</a>)}
|
||||
</div>
|
||||
<a href={post.url} class="post-link" title={format(new Date(post.date), 'MMMM do, yyyy')}><span class="svg-icon"><Link /></span> Permalink</a>
|
||||
</div>
|
||||
<div class="post-content">{post.content ? <Raw html={post.content} /> : <div class="loading"><Sync /></div>}</div>
|
||||
</article>
|
||||
);
|
||||
|
||||
class App extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const index = this.props.index;
|
||||
const hash = window.location.hash.slice(1);
|
||||
this.state = {
|
||||
hash,
|
||||
loading: false,
|
||||
posts: Object.fromEntries(index.posts.map((post) => [ post.id, post ])),
|
||||
tags: index.tags,
|
||||
authors: index.authors,
|
||||
latest: index.latest,
|
||||
};
|
||||
|
||||
this.loading = new Map();
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.hashedPosts = this.hashedPosts.bind(this);
|
||||
this.ensurePost = this.ensurePost.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('hashchange', this.onChange);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('hashchange', this.onChange);
|
||||
}
|
||||
|
||||
onChange () {
|
||||
this.setState({
|
||||
hash: window.location.hash.slice(1),
|
||||
prevHash: this.state.hash,
|
||||
});
|
||||
}
|
||||
|
||||
parseHash () {
|
||||
return this.state.hash && String(this.state.hash).split('=').filter(Boolean) || [];
|
||||
}
|
||||
|
||||
hashedPosts (target, value) {
|
||||
const posts = Object.values(this.state.posts);
|
||||
if (!target && !value) return [ this.state.latest ];
|
||||
return posts.filter((post) => {
|
||||
// console.log({ post, target, value })
|
||||
if (target === 'tag') return !!post.tags[value];
|
||||
if (target === 'author') return post.author.includes(value);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
ensurePost ({ id, json }) {
|
||||
if (this.loading.has(id)) return this.loading.get(id);
|
||||
const p = fetch(json)
|
||||
.then((res) => res.json())
|
||||
.then((post) => {
|
||||
this.setState({
|
||||
posts: { ...this.state.posts, [post.id]: post },
|
||||
});
|
||||
})
|
||||
.catch(console.error); // eslint-disable-line no-console
|
||||
|
||||
this.loading.set(id, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
render () {
|
||||
const [ target, value ] = this.parseHash();
|
||||
const posts = this.hashedPosts(target, value);
|
||||
|
||||
if (this.state.loading) {
|
||||
return <div class="loading"><Sync /></div>;
|
||||
}
|
||||
|
||||
posts.forEach(this.ensurePost);
|
||||
|
||||
let caption = null;
|
||||
if (target === 'tag') caption = <h4>Threads about {this.state.tags[value] || value}</h4>;
|
||||
if (target === 'author') caption = <h4>Tweets by {value}</h4>;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{caption}
|
||||
{map(posts, (post, i) =>
|
||||
<Post post={post} key={i} />,
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function run () {
|
||||
const index = await fetch('/tweets/index.json').then((res) => res.json());
|
||||
|
||||
|
||||
const target = document.querySelector('.post-index section');
|
||||
while (target.firstChild) {
|
||||
target.removeChild(target.firstChild);
|
||||
}
|
||||
render(<App index={index} />, target);
|
||||
}
|
||||
|
||||
run().catch(console.error); // eslint-disable-line
|
||||
12
js/.eslintrc
12
js/.eslintrc
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "twipped/browser",
|
||||
"env": {"es6": true, "jquery": true},
|
||||
"rules": {
|
||||
'indent': [ 2, 2, {
|
||||
'MemberExpression': 1,
|
||||
} ],
|
||||
'prefer-arrow-callback': 0,
|
||||
'object-shorthand': 0,
|
||||
'node/no-unsupported-features/node-builtins': 0
|
||||
}
|
||||
}
|
||||
27
js/.eslintrc.js
Normal file
27
js/.eslintrc.js
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
module.exports = exports = {
|
||||
extends: "twipped/browser",
|
||||
env: {es6: true, jquery: true},
|
||||
rules: {
|
||||
'indent': [ 2, 2, {
|
||||
'MemberExpression': 1,
|
||||
} ],
|
||||
'prefer-arrow-callback': 0,
|
||||
'object-shorthand': 0,
|
||||
'node/no-unsupported-features/node-builtins': 0
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: '$*.jsx',
|
||||
extends: "twipped/react",
|
||||
rules: {
|
||||
'react/jsx-indent': [2, 2, {checkAttributes: true}],
|
||||
"react/no-unknown-property": [2, { ignore: ['class'] }],
|
||||
'node/no-unpublished-import': 0,
|
||||
"node/no-missing-import": ["error", {
|
||||
"allowModules": ["svg", 'react']
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user