From 5e522974814a6fc5d3ca334079ac3641f562bd08 Mon Sep 17 00:00:00 2001 From: teddit Date: Sat, 21 Nov 2020 13:50:12 +0100 Subject: [PATCH] add dark theme and preferences --- app.js | 3 + dist/css/styles.css | 257 ++++++++++++++++++++++-- node_modules/cookie-parser/HISTORY.md | 95 +++++++++ node_modules/cookie-parser/LICENSE | 23 +++ node_modules/cookie-parser/README.md | 102 ++++++++++ node_modules/cookie-parser/index.js | 182 +++++++++++++++++ node_modules/cookie-parser/package.json | 85 ++++++++ package-lock.json | 11 +- package.json | 1 + routes.js | 161 ++++++++++----- views/about.pug | 2 +- views/index.pug | 2 +- views/post.pug | 2 +- views/preferences.pug | 16 +- views/search.pug | 2 +- views/subreddit.pug | 2 +- views/user.pug | 2 +- 17 files changed, 872 insertions(+), 76 deletions(-) create mode 100644 node_modules/cookie-parser/HISTORY.md create mode 100644 node_modules/cookie-parser/LICENSE create mode 100644 node_modules/cookie-parser/README.md create mode 100644 node_modules/cookie-parser/index.js create mode 100644 node_modules/cookie-parser/package.json diff --git a/app.js b/app.js index c09f9b0..54495dc 100644 --- a/app.js +++ b/app.js @@ -42,6 +42,7 @@ const pug = require('pug') const path = require('path') const compression = require('compression') const express = require('express') +const cookieParser = require('cookie-parser') const r = require('redis') const redis = r.createClient() const helmet = require('helmet') @@ -86,6 +87,8 @@ if(use_compression) { app.use(compression()) } +app.use(cookieParser()) + if(use_view_cache) { app.set('view cache', true) } diff --git a/dist/css/styles.css b/dist/css/styles.css index 94a7150..0b77e07 100644 --- a/dist/css/styles.css +++ b/dist/css/styles.css @@ -2,7 +2,10 @@ --sm-font: 0.666rem; --lightgray: #f5f5f5; --whitebg: #ffffff; + --darkbg: #0F0F0F; + --darkbglight: #252525; --linkcolor: #000bac; + --darklinkcolor: #599bff; --graytext: #6f6f6f; } * { @@ -10,6 +13,200 @@ margin: 0; font-family: sans-serif; } +/* Move themes to the beginning of the file to avoid themes flickering. */ +/* DARK THEME */ +body.dark { + background: var(--darkbg); + color: #cacaca; +} +body.dark nav { + background: #1f1f1f; +} +body.dark .top-links a { + background: var(--darkbg); + color: #bfbfbf; +} +body.dark header { + background: var(--darkbglight); + color: #f1f1f1; +} +body.dark #post header div a { + color: var(--darklinkcolor); + text-decoration: none; +} +body.dark a { + color: #f5f5f5; +} +body.dark a:hover, body.dark a:focus { + color: #3d8aff; + text-decoration: underline; +} +body.dark #post header div a:hover, +body.dark #post header div a:focus { + text-decoration: underline; +} +body.dark input[type="submit"]:hover, +body.dark input[type="submit"]:focus, +body.dark .btn:hover, +body.dark .btn:focus { + background: white; + color: black; + text-decoration: none; +} +body.dark form legend { + border-bottom: 1px solid #353535; +} +body.dark #post .title a { + color: var(--darklinkcolor); +} +body.dark #post .submitted { + color: #a5a5a5; +} +body.dark #post .usertext-body { + background: #0a0a0a; + border: 1px solid #404040; +} +body.dark #post .infobar { + background-color: #d2d2d2; + color: #2f2f2f; +} +body.dark #post .infobar a { + color: #0356d4; +} +body.dark header .tabmenu li a { + background: #3e3e3e; +} +body.dark header .tabmenu li a:hover, body.dark header .tabmenu li a:focus { + text-decoration: underline; + color: white; +} +body.dark #search { + color: #d2d2d2; +} +body.dark .md { + color: #dadada; +} +body.dark .md blockquote, body.dark .md del { + color: #777777; +} +body.dark .md code, body.dark .md pre { + background: black; + color: white; +} +body.dark .comment .body blockquote { + background: #313131; + color: #afafaf; + border-color: #464646; +} +body.dark .comment { + background: var(--darkbg); +} +body.dark .comment .comment { + background: var(--darkbglight); + border-left: 1px solid #545454; +} +body.dark .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +/* Is there any better way to do this??? send help naow */ +body.dark .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbg); +} +body.dark .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { + background: var(--darkbglight); +} +body.dark .comment .meta .created a { + color: #7b7b7b; +} +body.dark .comment details summary { + color: #868686; +} +body.dark .comment details summary::-webkit-details-marker, +body.dark .comment details summary::marker { + color: #868686; +} +body.dark #links .link .entry .title a h2 { + color: #f0f0f0; +} +body.dark #links .link .image .no-image, +body.dark #user .entry .image .no-image { + filter: opacity(0.5); +} +body.dark #links .link .upvotes { + color: #858585; +} +body.dark .upvotes .arrow, +body.dark .score .arrow { + filter: opacity(0.5); +} +body.dark #links .link .entry .meta a { + color: #c7c7c7; +} +body.dark .content .bottom img { + filter: invert(1); +} +body.dark .container .content { + border: 1px solid #5e5e5e; +} +body.dark input[type="submit"], +body.dark .btn { + background: black; + color: white; +} a { color: var(--linkcolor); text-decoration: none; @@ -33,7 +230,7 @@ a:hover, a:focus { background: url(/css/sprite.png?v=1); background-position: -84px -1654px; background-repeat: no-repeat; - margin: 4px 0px 0px 0px; + margin: 2px 0px 2px 0px; width: 100%; height: 14px; display: block; @@ -121,6 +318,15 @@ nav .settings a:hover,nav .settings a:focus { line-height: 1.4; padding-bottom: 5px; } +form legend { + border-bottom: 1px solid #e3e3e3; + margin-bottom: 10px; + padding-bottom: 10px; +} +.container .content p.notice { + padding-top: 20px; + padding-bottom: 20px; +} .container .content p.version { text-align: right; color: #4f4f4f; @@ -137,7 +343,7 @@ header { width: 100%; padding-top: 15px; margin-bottom: 21px; - margin-top: 14px; + margin-top: 2px; background: gainsboro; } header a { @@ -196,7 +402,27 @@ header .tabmenu li.active a { border-radius: 3px; font-weight: bold; } -/*SUBREDDIT LINKS*/ +input[type="submit"], +.btn { + padding: 3px; + margin-top: 7px; + margin-right: 10px; + border-radius: 0px; + border: 1px solid #a5a5a5; + background: white; + color: #464646; + font-size: 13px; +} +input[type="submit"]:focus, +input[type="submit"]:hover, +.btn:focus, +.btn:hover { + background: #4c4c4c; + color: white; + cursor: pointer; + text-decoration: none; +} +/* SUBREDDIT LINKS */ #links { float: left; width: 100%; @@ -327,7 +553,7 @@ header .tabmenu li.active a { #links.search .link .meta a.comments { margin-left: 0px; } -/*COMMENTS*/ +/* COMMENTS */ .comment { font-size: 0.83rem; } @@ -473,7 +699,7 @@ header .tabmenu li.active a { .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment .comment { background: var(--lightgray); } -/*POST*/ +/* POST */ #post .info { float: left; width: 100%; @@ -635,7 +861,7 @@ header .tabmenu li.active a { #post .gallery .item small { font-size: 10px; } -/*USER*/ +/* USER */ #user .entries { float: left; width: 80%; @@ -804,7 +1030,7 @@ header .tabmenu li.active a { #user .entries .entry a.context { margin-right: 10px; } -/*SEARCH*/ +/* SEARCH */ #search { margin-left: 30px; margin-bottom: 50px; @@ -841,20 +1067,7 @@ header .tabmenu li.active a { border-radius: 0px; margin-bottom: 11px; } -#search input[type="submit"] { - padding: 3px; - margin-top: 7px; - border-radius: 0px; - border: 1px solid #a5a5a5; - background: white; - color: #464646; -} -#search input[type="submit"]:focus, #search input[type="submit"]:hover { - background: #4c4c4c; - color: white; - cursor: pointer; -} -/*REDDIT STYLES*/ +/* REDDIT STYLES */ .md .md-spoiler-text { border-radius:2px; transition:background ease-out 1s; @@ -1065,7 +1278,7 @@ code { font-size:1em; line-height:1.25em; } -/*Fix spoiler texts not showing without JS*/ +/* Fix spoiler texts not showing without JS */ .md .md-spoiler-text:not(.revealed):active,.md .md-spoiler-text:not(.revealed):focus,.md .md-spoiler-text:not(.revealed):hover { color: black; background: #fff0; diff --git a/node_modules/cookie-parser/HISTORY.md b/node_modules/cookie-parser/HISTORY.md new file mode 100644 index 0000000..3405d50 --- /dev/null +++ b/node_modules/cookie-parser/HISTORY.md @@ -0,0 +1,95 @@ +1.4.5 / 2020-03-14 +================== + + * deps: cookie@0.4.0 + +1.4.4 / 2019-02-12 +================== + + * perf: normalize `secret` argument only once + +1.4.3 / 2016-05-26 +================== + + * deps: cookie@0.3.1 + - perf: use for loop in parse + +1.4.2 / 2016-05-20 +================== + + * deps: cookie@0.2.4 + - perf: enable strict mode + - perf: use for loop in parse + - perf: use string concatenation for serialization + +1.4.1 / 2016-01-11 +================== + + * deps: cookie@0.2.3 + * perf: enable strict mode + +1.4.0 / 2015-09-18 +================== + + * Accept array of secrets in addition to a single secret + * Fix `JSONCookie` to return `undefined` for non-string arguments + * Fix `signedCookie` to return `undefined` for non-string arguments + * deps: cookie@0.2.2 + +1.3.5 / 2015-05-19 +================== + + * deps: cookie@0.1.3 + - Slight optimizations + +1.3.4 / 2015-02-15 +================== + + * deps: cookie-signature@1.0.6 + +1.3.3 / 2014-09-05 +================== + + * deps: cookie-signature@1.0.5 + +1.3.2 / 2014-06-26 +================== + + * deps: cookie-signature@1.0.4 + - fix for timing attacks + +1.3.1 / 2014-06-17 +================== + + * actually export `signedCookie` + +1.3.0 / 2014-06-17 +================== + + * add `signedCookie` export for single cookie unsigning + +1.2.0 / 2014-06-17 +================== + + * export parsing functions + * `req.cookies` and `req.signedCookies` are now plain objects + * slightly faster parsing of many cookies + +1.1.0 / 2014-05-12 +================== + + * Support for NodeJS version 0.8 + * deps: cookie@0.1.2 + - Fix for maxAge == 0 + - made compat with expires field + - tweak maxAge NaN error message + +1.0.1 / 2014-02-20 +================== + + * add missing dependencies + +1.0.0 / 2014-02-15 +================== + + * Genesis from `connect` diff --git a/node_modules/cookie-parser/LICENSE b/node_modules/cookie-parser/LICENSE new file mode 100644 index 0000000..343f2ad --- /dev/null +++ b/node_modules/cookie-parser/LICENSE @@ -0,0 +1,23 @@ +(The MIT License) + +Copyright (c) 2014 TJ Holowaychuk +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/cookie-parser/README.md b/node_modules/cookie-parser/README.md new file mode 100644 index 0000000..bc0d271 --- /dev/null +++ b/node_modules/cookie-parser/README.md @@ -0,0 +1,102 @@ +# cookie-parser + +[![NPM Version][npm-version-image]][npm-url] +[![NPM Downloads][npm-downloads-image]][npm-url] +[![Build Status][travis-image]][travis-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +Parse `Cookie` header and populate `req.cookies` with an object keyed by the +cookie names. Optionally you may enable signed cookie support by passing a +`secret` string, which assigns `req.secret` so it may be used by other +middleware. + +## Installation + +```sh +$ npm install cookie-parser +``` + +## API + +```js +var express = require('express') +var cookieParser = require('cookie-parser') + +var app = express() +app.use(cookieParser()) +``` + +### cookieParser(secret, options) + +- `secret` a string or array used for signing cookies. This is optional and if + not specified, will not parse signed cookies. If a string is provided, this + is used as the secret. If an array is provided, an attempt will be made to + unsign the cookie with each secret in order. +- `options` an object that is passed to `cookie.parse` as the second option. Se + [cookie](https://www.npmjs.org/package/cookie) for more information. + - `decode` a function to decode the value of the cookie + +### cookieParser.JSONCookie(str) + +Parse a cookie value as a JSON cookie. This will return the parsed JSON value +if it was a JSON cookie, otherwise, it will return the passed value. + +### cookieParser.JSONCookies(cookies) + +Given an object, this will iterate over the keys and call `JSONCookie` on each +value, replacing the original value with the parsed value. This returns the +same object that was passed in. + +### cookieParser.signedCookie(str, secret) + +Parse a cookie value as a signed cookie. This will return the parsed unsigned +value if it was a signed cookie and the signature was valid. If the value was +not signed, the original value is returned. If the value was signed but the +signature could not be validated, `false` is returned. + +The `secret` argument can be an array or string. If a string is provided, this +is used as the secret. If an array is provided, an attempt will be made to +unsign the cookie with each secret in order. + +### cookieParser.signedCookies(cookies, secret) + +Given an object, this will iterate over the keys and check if any value is a +signed cookie. If it is a signed cookie and the signature is valid, the key +will be deleted from the object and added to the new object that is returned. + +The `secret` argument can be an array or string. If a string is provided, this +is used as the secret. If an array is provided, an attempt will be made to +unsign the cookie with each secret in order. + +## Example + +```js +var express = require('express') +var cookieParser = require('cookie-parser') + +var app = express() +app.use(cookieParser()) + +app.get('/', function (req, res) { + // Cookies that have not been signed + console.log('Cookies: ', req.cookies) + + // Cookies that have been signed + console.log('Signed Cookies: ', req.signedCookies) +}) + +app.listen(8080) + +// curl command that sends an HTTP request with two cookies +// curl http://127.0.0.1:8080 --cookie "Cho=Kim;Greet=Hello" +``` + +### [MIT Licensed](LICENSE) + +[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/cookie-parser/master +[coveralls-url]: https://coveralls.io/r/expressjs/cookie-parser?branch=master +[npm-downloads-image]: https://badgen.net/npm/dm/cookie-parser +[npm-url]: https://npmjs.org/package/cookie-parser +[npm-version-image]: https://badgen.net/npm/v/cookie-parser +[travis-image]: https://badgen.net/travis/expressjs/cookie-parser/master +[travis-url]: https://travis-ci.org/expressjs/cookie-parser diff --git a/node_modules/cookie-parser/index.js b/node_modules/cookie-parser/index.js new file mode 100644 index 0000000..dd6d479 --- /dev/null +++ b/node_modules/cookie-parser/index.js @@ -0,0 +1,182 @@ +/*! + * cookie-parser + * Copyright(c) 2014 TJ Holowaychuk + * Copyright(c) 2015 Douglas Christopher Wilson + * MIT Licensed + */ + +'use strict' + +/** + * Module dependencies. + * @private + */ + +var cookie = require('cookie') +var signature = require('cookie-signature') + +/** + * Module exports. + * @public + */ + +module.exports = cookieParser +module.exports.JSONCookie = JSONCookie +module.exports.JSONCookies = JSONCookies +module.exports.signedCookie = signedCookie +module.exports.signedCookies = signedCookies + +/** + * Parse Cookie header and populate `req.cookies` + * with an object keyed by the cookie names. + * + * @param {string|array} [secret] A string (or array of strings) representing cookie signing secret(s). + * @param {Object} [options] + * @return {Function} + * @public + */ + +function cookieParser (secret, options) { + var secrets = !secret || Array.isArray(secret) + ? (secret || []) + : [secret] + + return function cookieParser (req, res, next) { + if (req.cookies) { + return next() + } + + var cookies = req.headers.cookie + + req.secret = secrets[0] + req.cookies = Object.create(null) + req.signedCookies = Object.create(null) + + // no cookies + if (!cookies) { + return next() + } + + req.cookies = cookie.parse(cookies, options) + + // parse signed cookies + if (secrets.length !== 0) { + req.signedCookies = signedCookies(req.cookies, secrets) + req.signedCookies = JSONCookies(req.signedCookies) + } + + // parse JSON cookies + req.cookies = JSONCookies(req.cookies) + + next() + } +} + +/** + * Parse JSON cookie string. + * + * @param {String} str + * @return {Object} Parsed object or undefined if not json cookie + * @public + */ + +function JSONCookie (str) { + if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') { + return undefined + } + + try { + return JSON.parse(str.slice(2)) + } catch (err) { + return undefined + } +} + +/** + * Parse JSON cookies. + * + * @param {Object} obj + * @return {Object} + * @public + */ + +function JSONCookies (obj) { + var cookies = Object.keys(obj) + var key + var val + + for (var i = 0; i < cookies.length; i++) { + key = cookies[i] + val = JSONCookie(obj[key]) + + if (val) { + obj[key] = val + } + } + + return obj +} + +/** + * Parse a signed cookie string, return the decoded value. + * + * @param {String} str signed cookie string + * @param {string|array} secret + * @return {String} decoded value + * @public + */ + +function signedCookie (str, secret) { + if (typeof str !== 'string') { + return undefined + } + + if (str.substr(0, 2) !== 's:') { + return str + } + + var secrets = !secret || Array.isArray(secret) + ? (secret || []) + : [secret] + + for (var i = 0; i < secrets.length; i++) { + var val = signature.unsign(str.slice(2), secrets[i]) + + if (val !== false) { + return val + } + } + + return false +} + +/** + * Parse signed cookies, returning an object containing the decoded key/value + * pairs, while removing the signed key from obj. + * + * @param {Object} obj + * @param {string|array} secret + * @return {Object} + * @public + */ + +function signedCookies (obj, secret) { + var cookies = Object.keys(obj) + var dec + var key + var ret = Object.create(null) + var val + + for (var i = 0; i < cookies.length; i++) { + key = cookies[i] + val = obj[key] + dec = signedCookie(val, secret) + + if (val !== dec) { + ret[key] = dec + delete obj[key] + } + } + + return ret +} diff --git a/node_modules/cookie-parser/package.json b/node_modules/cookie-parser/package.json new file mode 100644 index 0000000..061ac91 --- /dev/null +++ b/node_modules/cookie-parser/package.json @@ -0,0 +1,85 @@ +{ + "_from": "cookie-parser", + "_id": "cookie-parser@1.4.5", + "_inBundle": false, + "_integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", + "_location": "/cookie-parser", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "cookie-parser", + "name": "cookie-parser", + "escapedName": "cookie-parser", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", + "_shasum": "3e572d4b7c0c80f9c61daf604e4336831b5d1d49", + "_spec": "cookie-parser", + "_where": "/home/teddit/site", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "url": "http://tjholowaychuk.com" + }, + "bugs": { + "url": "https://github.com/expressjs/cookie-parser/issues" + }, + "bundleDependencies": false, + "contributors": [ + { + "name": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com" + } + ], + "dependencies": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6" + }, + "deprecated": false, + "description": "Parse HTTP request cookies", + "devDependencies": { + "eslint": "6.8.0", + "eslint-config-standard": "14.1.0", + "eslint-plugin-import": "2.20.1", + "eslint-plugin-markdown": "1.0.2", + "eslint-plugin-node": "11.0.0", + "eslint-plugin-promise": "4.2.1", + "eslint-plugin-standard": "4.0.1", + "istanbul": "0.4.5", + "mocha": "7.1.0", + "supertest": "4.0.2" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "homepage": "https://github.com/expressjs/cookie-parser#readme", + "keywords": [ + "cookie", + "middleware" + ], + "license": "MIT", + "name": "cookie-parser", + "repository": { + "type": "git", + "url": "git+https://github.com/expressjs/cookie-parser.git" + }, + "scripts": { + "lint": "eslint --plugin markdown --ext js,md .", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.4.5" +} diff --git a/package-lock.json b/package-lock.json index d5c5b28..f8bc2be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "teddit", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -233,6 +233,15 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, + "cookie-parser": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", + "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6" + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/package.json b/package.json index aa391ed..360c396 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "app.js", "dependencies": { "compression": "^1.7.4", + "cookie-parser": "^1.4.5", "express": "^4.17.1", "fs": "^0.0.1-security", "helmet": "^4.2.0", diff --git a/routes.js b/routes.js index 2bbb682..2230ebf 100644 --- a/routes.js +++ b/routes.js @@ -29,7 +29,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.get(key, (error, json) => { if(error) { console.error('Error getting the frontpage key from redis.', error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(json) { console.log('Got frontpage key from redis.'); @@ -38,7 +38,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { return res.render('index', { json: processed_json, sortby: 'hot', - past: past + past: past, + user_preferences: req.cookies }) })() } else { @@ -50,7 +51,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.setex(key, setexs.frontpage, JSON.stringify(json), (error) => { if(error) { console.error('Error setting the frontpage key to redis.', error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } else { console.log('Fetched the frontpage from reddit API.'); (async () => { @@ -58,7 +59,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { return res.render('index', { json: processed_json, sortby: 'hot', - past: past + past: past, + user_preferences: req.cookies }) })() } @@ -67,7 +69,11 @@ module.exports = (app, redis, fetch, RedditAPI) => { } else { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) } }).catch(error => { console.error('Error fetching the frontpage JSON file.', error) @@ -77,11 +83,16 @@ module.exports = (app, redis, fetch, RedditAPI) => { }) app.get('/about', (req, res, next) => { - return res.render('about') + return res.render('about', { user_preferences: req.cookies }) }) app.get('/preferences', (req, res, next) => { - return res.render('preferences') + return res.render('preferences', { user_preferences: req.cookies }) + }) + + app.get('/resetprefs', (req, res, next) => { + res.clearCookie('theme') + return res.redirect('/preferences') }) app.get('/search', (req, res, next) => { @@ -150,7 +161,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.get(key, (error, json) => { if(error) { console.error('Error getting the frontpage with sortby key from redis.', error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(json) { console.log('Got frontpage with sortyby key from redis.'); @@ -159,7 +170,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { return res.render('index', { json: processed_json, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) })() } else { @@ -171,7 +183,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.setex(key, setexs.frontpage, JSON.stringify(json), (error) => { if(error) { console.error('Error setting the frontpage with sortby key to redis.', error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } else { console.log('Fetched the frontpage with sortby from reddit API.'); (async () => { @@ -179,7 +191,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { return res.render('index', { json: processed_json, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) })() } @@ -188,7 +201,11 @@ module.exports = (app, redis, fetch, RedditAPI) => { } else { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) } }).catch(error => { console.error('Error fetching the frontpage with sortby JSON file.', error) @@ -229,7 +246,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.get(key, (error, json) => { if(error) { console.error('Error getting the search key from redis.', error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(json) { console.log('Got search key from redis.'); @@ -242,7 +259,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { nsfw: nsfw, subreddit: subreddit, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) })() } else { @@ -254,7 +272,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.setex(key, setexs.searches, JSON.stringify(json), (error) => { if(error) { console.error('Error setting the searches key to redis.', error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } else { console.log('Fetched search results from reddit API.'); (async () => { @@ -266,7 +284,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { nsfw: nsfw, subreddit: subreddit, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) })() } @@ -275,7 +294,11 @@ module.exports = (app, redis, fetch, RedditAPI) => { } else { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) } }).catch(error => { console.error('Error fetching the frontpage JSON file.', error) @@ -323,7 +346,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.get(key, (error, json) => { if(error) { console.error(`Error getting the ${subreddit} key from redis.`, error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(json) { console.log(`Got /r/${subreddit} key from redis.`); @@ -335,10 +358,16 @@ module.exports = (app, redis, fetch, RedditAPI) => { subreddit: subreddit, subreddit_front: (!before && !after) ? true : false, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) } else { - return res.render('subreddit', { json: null, error: true, data: processed_json }) + return res.render('subreddit', { + json: null, + error: true, + data: processed_json, + user_preferences: req.cookies + }) } })() } else { @@ -350,7 +379,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.setex(key, setexs.subreddit, JSON.stringify(json), (error) => { if(error) { console.error(`Error setting the ${subreddit} key to redis.`, error) - return res.render('subreddit', { json: null }) + return res.render('subreddit', { json: null, user_preferences: req.cookies }) } else { console.log(`Fetched the JSON from reddit.com/r/${subreddit}.`); (async () => { @@ -360,7 +389,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { subreddit: subreddit, subreddit_front: (!before && !after) ? true : false, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) })() } @@ -373,7 +403,11 @@ module.exports = (app, redis, fetch, RedditAPI) => { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) } - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) } }).catch(error => { console.error(`Error fetching the JSON file from reddit.com/r/${subreddit}.`, error) @@ -402,7 +436,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.get(comments_url, (error, json) => { if(error) { console.error(`Error getting the ${comments_url} key from redis.`, error) - return res.render('index', { post: null }) + return res.render('index', { post: null, user_preferences: req.cookies }) } if(json) { console.log(`Got ${comments_url} key from redis.`); @@ -415,21 +449,22 @@ module.exports = (app, redis, fetch, RedditAPI) => { comments: finalized_json.comments, viewing_comment: viewing_comment, post_url: post_url, - subreddit: subreddit + subreddit: subreddit, + user_preferences: req.cookies }) } else { let key = `morechildren:${post_url};1` redis.get(key, (error, json) => { if(error) { console.error(`Error getting the ${key} key from redis.`, error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(json) { console.log(`Got ${key} key from redis.`); redis.get(post_url, (error, post_json) => { if(error) { console.error(`Error getting the ${post_url} key from redis.`, error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(post_json) { redis.get(`morechildren_ids:${post_url}`, (error, morechildren_ids) => { @@ -446,7 +481,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { viewing_comment: false, post_url: post_url, subreddit: req.params.subreddit, - more_comments_page: 1 + more_comments_page: 1, + user_preferences: req.cookies }) })() }) @@ -465,7 +501,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.setex(comments_url, setexs.posts, JSON.stringify(json), (error) => { if(error) { console.error(`Error setting the ${comments_url} key to redis.`, error) - return res.render('post', { post: null }) + return res.render('post', { post: null, user_preferences: req.cookies }) } else { console.log(`Fetched the JSON from reddit.com${comments_url}.`); (async () => { @@ -476,7 +512,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { comments: finalized_json.comments, viewing_comment: viewing_comment, post_url: post_url, - subreddit: subreddit + subreddit: subreddit, + user_preferences: req.cookies }) })() } @@ -489,7 +526,12 @@ module.exports = (app, redis, fetch, RedditAPI) => { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) } - return res.render('index', { json: null, http_status_code: result.status, http_statustext: result.statusText }) + return res.render('index', { + json: null, + http_status_code: result.status, + http_statustext: result.statusText, + user_preferences: req.cookies + }) } }).catch(error => { console.error(`Error fetching the JSON file from reddit.com${comments_url}.`, error) @@ -551,7 +593,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.get(key, (error, json) => { if(error) { console.error(`Error getting the user ${key} key from redis.`, error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(json) { console.log(`Got user ${user} key from redis.`); @@ -560,7 +602,8 @@ module.exports = (app, redis, fetch, RedditAPI) => { return res.render('user', { data: processed_json, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) })() } else { @@ -579,14 +622,15 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.setex(key, setexs.user, JSON.stringify(user_data), (error) => { if(error) { console.error(`Error setting the user ${key} key to redis.`, error) - return res.render('index', { post: null }) + return res.render('index', { post: null, user_preferences: req.cookies }) } else { (async () => { let processed_json = await processJsonUser(user_data, true, after, before) return res.render('user', { data: processed_json, sortby: sortby, - past: past + past: past, + user_preferences: req.cookies }) })() } @@ -595,11 +639,19 @@ module.exports = (app, redis, fetch, RedditAPI) => { } else { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) } }).catch(error => { console.error(`Error fetching the overview JSON file from reddit.com/u/${user}`, error) - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) }) }) } else { @@ -609,7 +661,12 @@ module.exports = (app, redis, fetch, RedditAPI) => { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) } - return res.render('index', { json: null, http_status_code: result.status, http_statustext: result.statusText }) + return res.render('index', { + json: null, + http_status_code: result.status, + http_statustext: result.statusText, + user_preferences: req.cookies + }) } }).catch(error => { console.error(`Error fetching the about JSON file from reddit.com/u/${user}`, error) @@ -622,6 +679,12 @@ module.exports = (app, redis, fetch, RedditAPI) => { /** * POSTS */ + + app.post('/saveprefs', (req, res, next) => { + let theme = req.body.theme + res.cookie('theme', theme, { maxAge: 900000, httpOnly: true }) + return res.redirect('/preferences') + }) app.post('/r/:subreddit/comments/:id/:snippet', (req, res, next) => { /* morechildren route */ @@ -629,7 +692,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { let post_url = req.body.url if(!all_ids || !post_url || !post_url.startsWith('/r/')) { - return res.render('index', null) + return res.render('index', { json: null, user_preferences: req.cookies }) } else { let post_id = post_url.split('/')[4] let ids_to_show = '' @@ -645,7 +708,7 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.get(key, (error, json) => { if(error) { console.error(`Error getting the ${key} key from redis.`, error) - return res.render('index', { json: null }) + return res.render('index', { json: null, user_preferences: req.cookies }) } if(json) { console.log(`Redirecting to ${post_url} with cursor...`); @@ -661,11 +724,11 @@ module.exports = (app, redis, fetch, RedditAPI) => { redis.setex(key, setexs.posts, JSON.stringify(comments), (error) => { if(error) { console.error(`Error setting the ${key} key to redis.`, error) - return res.render('post', { post: null }) + return res.render('post', { post: null, user_preferences: req.cookies }) } else { redis.setex(`morechildren_ids:${post_url}`, setexs.posts, JSON.stringify(all_ids)) - console.log(`Fetched the JSON from reddit API (endpoint "morechildren") with url: ${url}.`); - console.log(`Redirecting to ${post_url} with cursor...`); + console.log(`Fetched the JSON from reddit API (endpoint "morechildren") with url: ${url}.`) + console.log(`Redirecting to ${post_url} with cursor...`) return res.redirect(`${post_url}?cursor=${page}&page=${page}`) } }) @@ -673,11 +736,19 @@ module.exports = (app, redis, fetch, RedditAPI) => { } else { console.error(`Something went wrong while fetching data from reddit API. ${result.status} – ${result.statusText}`) console.error(reddit_api_error_text) - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) } }).catch(error => { console.log(`Error fetching the JSON from reddit API (endpoint "morechildren") with url: ${url}.`, error) - return res.render('index', { json: null, http_status_code: result.status }) + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies + }) }) } }) diff --git a/views/about.pug b/views/about.pug index a4a6737..b1ade7a 100644 --- a/views/about.pug +++ b/views/about.pug @@ -3,7 +3,7 @@ html head title about - teddit include includes/head.pug - body + body(class=""+ user_preferences.theme +"") include includes/topbar.pug .container .content diff --git a/views/index.pug b/views/index.pug index cdadc9d..7b219bb 100644 --- a/views/index.pug +++ b/views/index.pug @@ -3,7 +3,7 @@ html head title teddit include includes/head.pug - body + body(class=""+ user_preferences.theme +"") include includes/topbar.pug if json === null h2 error diff --git a/views/post.pug b/views/post.pug index bf51a7e..e33f5f0 100644 --- a/views/post.pug +++ b/views/post.pug @@ -3,7 +3,7 @@ html head title #{post.title} : #{subreddit} include includes/head.pug - body + body(class=""+ user_preferences.theme +"") include includes/topbar.pug if post === null h1 Error occured diff --git a/views/preferences.pug b/views/preferences.pug index 2c9cd4b..ac54806 100644 --- a/views/preferences.pug +++ b/views/preferences.pug @@ -3,9 +3,21 @@ html head title preferences - teddit include includes/head.pug - body + body(class=""+ user_preferences.theme +"") include includes/topbar.pug .container .content h1 Preferences - p nothing here yet + form(action="/saveprefs", method="POST") + legend Display + label(for="theme") Theme: + select(id="theme", name="theme") + if(!user_preferences.theme || user_preferences.theme == '') + option(value="", selected="selected") White + option(value="dark") Dark + if(user_preferences.theme === 'dark') + option(value="") White + option(value="dark", selected="selected") Dark + p(class="notice") Preferences are stored client-side using cookies without any personal information. + input(type="submit", value="Save preferences") + a(href="/resetprefs", class="btn") Reset preferences diff --git a/views/search.pug b/views/search.pug index cc9ff68..f98c821 100644 --- a/views/search.pug +++ b/views/search.pug @@ -3,7 +3,7 @@ html head title search results for #{q} include includes/head.pug - body + body(class=""+ user_preferences.theme +"") include includes/topbar.pug #search form(action="/r/" + subreddit + "/search", method="GET") diff --git a/views/subreddit.pug b/views/subreddit.pug index ef0143e..54fd4d2 100644 --- a/views/subreddit.pug +++ b/views/subreddit.pug @@ -3,7 +3,7 @@ html head title /r/#{subreddit} include includes/head.pug - body + body(class=""+ user_preferences.theme +"") include includes/topbar.pug if json === null h1 Error occured diff --git a/views/user.pug b/views/user.pug index cfa8083..112d880 100644 --- a/views/user.pug +++ b/views/user.pug @@ -3,7 +3,7 @@ html head title overview for #{data.username} include includes/head.pug - body + body(class=""+ user_preferences.theme +"") include includes/topbar.pug if user === null h1 Error occured