Set title on article and comment pages, add comment anchors

master
Tanner Collin 5 years ago
parent 5fd4fdb21c
commit 25a671f58e
  1. 2
      webclient/package.json
  2. 5
      webclient/public/index.html
  3. 6
      webclient/src/App.js
  4. 9
      webclient/src/Article.js
  5. 24
      webclient/src/Comments.js
  6. 13
      webclient/src/Feed.js
  7. 2
      webclient/src/Style-light.css
  8. 4
      webclient/src/utils.js
  9. 36
      webclient/yarn.lock

@ -6,7 +6,9 @@
"moment": "^2.24.0",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-helmet": "^5.2.1",
"react-router-dom": "^5.0.1",
"react-router-hash-link": "^1.2.2",
"react-scripts": "3.1.1"
},
"scripts": {

@ -5,8 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta
name="description"
content="Reddit, Hacker News, and Tildes combined, then pre-rendered in reader mode."
content="{{ description }}"
/>
<meta content="{{ url }}" name="og:site_name">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
@ -25,7 +26,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>QNN - Qot News Network</title>
<title>{{ title }} - QotNews</title>
<style>
html {

@ -36,15 +36,15 @@ class App extends React.Component {
<Router>
<div className='container menu'>
<p>
<Link to='/'>QNN - Home</Link>
<Link to='/'>QotNews - Feed</Link>
<span className='theme'>Theme: <a href='#' onClick={() => this.light()}>Light</a> - <a href='#' onClick={() => this.dark()}>Dark</a></span>
<br />
<span className='slogan'>Reddit, Hacker News, and Tildes combined, then pre-rendered in reader mode.</span>
</p>
</div>
<Route path='/' exact component={Feed} />
<Route path='/:id' exact component={Comments} />
<Route path='/:id/a' exact component={Article} />
<Route path='/:id/c' exact component={Comments} />
<Route path='/:id' exact component={Article} />
<ScrollToTop />
</Router>

@ -1,9 +1,8 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { sourceLink, infoLine, ToggleDot } from './utils.js';
const apiUrl = 'https://news-api.t0.vc/';
class Article extends React.Component {
constructor(props) {
super(props);
@ -19,7 +18,7 @@ class Article extends React.Component {
componentDidMount() {
const id = this.props.match.params.id;
fetch(apiUrl + id)
fetch('/api/' + id)
.then(res => res.json())
.then(
(result) => {
@ -42,6 +41,10 @@ class Article extends React.Component {
{error && <p>Connection error?</p>}
{story ?
<div className='article'>
<Helmet>
<title>{story.title} - QotNews</title>
</Helmet>
<h1>{story.title}</h1>
<div className='info'>

@ -1,10 +1,10 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { HashLink } from 'react-router-hash-link';
import { Helmet } from 'react-helmet';
import moment from 'moment';
import { sourceLink, infoLine, ToggleDot } from './utils.js';
const apiUrl = 'https://news-api.t0.vc/';
class Article extends React.Component {
constructor(props) {
super(props);
@ -20,12 +20,17 @@ class Article extends React.Component {
componentDidMount() {
const id = this.props.match.params.id;
fetch(apiUrl + id)
fetch('/api/' + id)
.then(res => res.json())
.then(
(result) => {
this.setState({ story: result.story });
localStorage.setItem(id, JSON.stringify(result.story));
this.setState({ story: result.story }, () => {
const hash = window.location.hash.substring(1);
if (hash) {
document.getElementById(hash).scrollIntoView();
}
});
},
(error) => {
this.setState({ error: true });
@ -37,7 +42,10 @@ class Article extends React.Component {
return (
<div className={level ? 'comment lined' : 'comment'}>
<div className='info'>
<p>{c.author === story.author ? '[OP]' : ''} {c.author || '[Deleted]'} | {moment.unix(c.date).fromNow()}</p>
<p>
{c.author === story.author ? '[OP]' : ''} {c.author || '[Deleted]'}
&#8203; | <HashLink to={'#'+c.author+c.date} id={c.author+c.date}>{moment.unix(c.date).fromNow()}</HashLink>
</p>
</div>
<div className='text' dangerouslySetInnerHTML={{ __html: c.text }} />
@ -61,10 +69,14 @@ class Article extends React.Component {
{error && <p>Connection error?</p>}
{story ?
<div className='article'>
<Helmet>
<title>{story.title} - QotNews Comments</title>
</Helmet>
<h1>{story.title}</h1>
<div className='info'>
<Link to={'/' + story.id + '/a'}>View article</Link>
<Link to={'/' + story.id}>View article</Link>
</div>
{infoLine(story)}

@ -1,10 +1,9 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { siteLogo, sourceLink, infoLine } from './utils.js';
import { clearStorage } from './utils.js';
const apiUrl = 'https://news-api.t0.vc/';
class Feed extends React.Component {
constructor(props) {
super(props);
@ -16,7 +15,7 @@ class Feed extends React.Component {
}
componentDidMount() {
fetch(apiUrl)
fetch('/api')
.then(res => res.json())
.then(
(result) => {
@ -24,7 +23,7 @@ class Feed extends React.Component {
clearStorage();
localStorage.setItem('stories', JSON.stringify(result.stories));
result.stories.filter(x => x.score >= 20).slice(0, 25).forEach(x => {
fetch(apiUrl + x.id)
fetch('/api/' + x.id)
.then(res => res.json())
.then(result => {
localStorage.setItem(x.id, JSON.stringify(result.story));
@ -45,6 +44,10 @@ class Feed extends React.Component {
return (
<div className='container'>
<Helmet>
<title>Feed - QotNews</title>
<meta name="description" content="Reddit, Hacker News, and Tildes combined, then pre-rendered in reader mode" />
</Helmet>
{error && <p>Connection error?</p>}
{stories ?
<div>
@ -55,7 +58,7 @@ class Feed extends React.Component {
</div>
<div className='title'>
<Link className='link' to={'/' + x.id + '/a'}>{siteLogo[x.source]} {x.title}</Link>
<Link className='link' to={'/' + x.id}>{siteLogo[x.source]} {x.title}</Link>
<span className='source'>
&#8203;({sourceLink(x)})

@ -2,7 +2,7 @@ body {
text-rendering: optimizeLegibility;
font: 1rem/1.3 sans-serif;
color: #000000;
margin-bottom: 50%;
margin-bottom: 100vh;
}
a {

@ -23,7 +23,7 @@ export const infoLine = (story) =>
by <a href={story.author_link}>{story.author}</a>
&#8203; {moment.unix(story.date).fromNow()}
&#8203; on <a href={story.link}>{story.source}</a> | &#8203;
<Link className={story.num_comments > 99 ? 'hot' : ''} to={'/' + story.id}>
<Link className={story.num_comments > 99 ? 'hot' : ''} to={'/' + story.id + '/c'}>
{story.num_comments} comment{story.num_comments !== 1 && 's'}
</Link>
</div>
@ -42,7 +42,7 @@ export class ToggleDot extends React.Component {
return (
<div className='toggleDot'>
<div className='button'>
<Link to={'/' + id + (article ? '/a' : '')}>
<Link to={'/' + id + (article ? '' : '/c')}>
<img src={Switch} />
</Link>
</div>

@ -7723,7 +7723,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.3"
prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -7937,6 +7937,21 @@ react-error-overlay@^6.0.1:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.1.tgz#b8d3cf9bb991c02883225c48044cb3ee20413e0f"
integrity sha512-V9yoTr6MeZXPPd4nV/05eCBvGH9cGzc52FN8fs0O0TVQ3HYYf1n7EgZVtHbldRq5xU9zEzoXIITjYNIfxDDdUw==
react-fast-compare@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-helmet@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.1.tgz#16a7192fdd09951f8e0fe22ffccbf9bb3e591ffa"
integrity sha512-CnwD822LU8NDBnjCpZ4ySh8L6HYyngViTZLfBBb3NjtrpN8m49clH8hidHouq20I51Y6TpCTISCBbqiY5GamwA==
dependencies:
object-assign "^4.1.1"
prop-types "^15.5.4"
react-fast-compare "^2.0.2"
react-side-effect "^1.1.0"
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.9.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb"
@ -7955,6 +7970,13 @@ react-router-dom@^5.0.1:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router-hash-link@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-1.2.2.tgz#7a0ad5e925d49596d19554de8bc6c554ce4f8099"
integrity sha512-LBthLVHdqPeKDVt3+cFRhy15Z7veikOvdKRZRfyBR2vjqIE7rxn+tKLjb6DOmLm6JpoQVemVDnxQ35RVnEHdQA==
dependencies:
prop-types "^15.6.0"
react-router@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.1.tgz#04ee77df1d1ab6cb8939f9f01ad5702dbadb8b0f"
@ -8032,6 +8054,13 @@ react-scripts@3.1.1:
optionalDependencies:
fsevents "2.0.7"
react-side-effect@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.2.0.tgz#0e940c78faba0c73b9b0eba9cd3dda8dfb7e7dae"
integrity sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w==
dependencies:
shallowequal "^1.0.1"
react@^16.9.0:
version "16.9.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa"
@ -8643,6 +8672,11 @@ shallow-clone@^3.0.0:
dependencies:
kind-of "^6.0.2"
shallowequal@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"

Loading…
Cancel
Save