Set title on article and comment pages, add comment anchors
This commit is contained in:
		| @@ -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]'} | ||||
| 						​ | <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'> | ||||
| 										​({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> | ||||
| 		​ {moment.unix(story.date).fromNow()} | ||||
| 		​ on <a href={story.link}>{story.source}</a> | ​ | ||||
| 		<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" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user