diff --git a/README.md b/README.md
index 6bf7cd8..333439d 100644
--- a/README.md
+++ b/README.md
@@ -26,10 +26,10 @@ Community instances:
* [https://teddit.ggc-project.de](https://teddit.ggc-project.de)
* [https://teddit.kavin.rocks](https://teddit.kavin.rocks)
+* [https://teddit.zaggy.nl](https://teddit.zaggy.nl)
## TODO
-* Import/export preferences
* User trophies
* "other discussions" feature
* "Open on reddit" links
@@ -37,7 +37,6 @@ Community instances:
## Roadmap
-* More themes, not just white or dark
* HLS video streaming? (Would require browser JavaScript)
* Onion site
* User login, so people can use their Reddit account through teddit to comment and up/downvote posts etc.
diff --git a/app.js b/app.js
index 2ae20d8..872d944 100644
--- a/app.js
+++ b/app.js
@@ -11,7 +11,6 @@ const express = require('express')
const cookieParser = require('cookie-parser')
const r = require('redis')
-
const redis = (() => {
if (!config.redis_enabled) {
// Stub Redis if disabled
diff --git a/inc/processJsonUser.js b/inc/processJsonUser.js
index 05ae464..f80ed78 100644
--- a/inc/processJsonUser.js
+++ b/inc/processJsonUser.js
@@ -1,6 +1,6 @@
module.exports = function() {
const config = require('../config');
- this.processJsonUser = function(json, parsed, after, before, user_preferences) {
+ this.processJsonUser = function(json, parsed, after, before, user_preferences, kind, post_type) {
return new Promise(resolve => {
(async () => {
if(!parsed) {
@@ -40,7 +40,10 @@ module.exports = function() {
let post_id = post.permalink.split('/').slice(-2)[0] + '/'
let url = post.permalink.replace(post_id, '')
-
+
+ if(type !== kind && kind)
+ continue
+
if(post.over_18)
if((config.nsfw_enabled === false && user_preferences.nsfw_enabled != 'true') || user_preferences.nsfw_enabled === 'false')
continue
@@ -103,6 +106,7 @@ module.exports = function() {
comment_karma: about.comment_karma,
view_more_posts: view_more_posts,
user_front: user_front,
+ post_type: post_type,
before: before,
after: after,
posts: posts
diff --git a/inc/teddit_api/handleSubreddit.js b/inc/teddit_api/handleSubreddit.js
index 261cf7e..058d783 100644
--- a/inc/teddit_api/handleSubreddit.js
+++ b/inc/teddit_api/handleSubreddit.js
@@ -69,6 +69,7 @@ module.exports = function() {
${link.title}
${link.author}
${link.created}
+ ${new Date(link.created_utc*1000).toGMTString()}
${link.domain}
${link.id}
${thumbnail}
diff --git a/inc/teddit_api/handleUser.js b/inc/teddit_api/handleUser.js
index 110f52f..2aba285 100644
--- a/inc/teddit_api/handleUser.js
+++ b/inc/teddit_api/handleUser.js
@@ -74,6 +74,7 @@ module.exports = function() {
${kind}
${post.subreddit}
${post.created_utc}
+ ${new Date(post.created_utc*1000).toGMTString()}
${post.ups}
${permalink}
${post.edited}
diff --git a/routes.js b/routes.js
index 2764ce2..bc4b978 100644
--- a/routes.js
+++ b/routes.js
@@ -28,6 +28,42 @@ module.exports = (app, redis, fetch, RedditAPI) => {
res.clearCookie('subbed_subreddits')
return res.redirect('/preferences')
})
+
+ app.get('/import_prefs/:key', (req, res, next) => {
+ let key = req.params.key
+ if(!key)
+ return res.redirect('/')
+ if(key.length !== 10)
+ return res.redirect('/')
+
+ key = `prefs_key:${key}`
+ redis.get(key, (error, json) => {
+ if(error) {
+ console.error(`Error getting the preferences import key ${key} from redis.`, error)
+ return res.render('index', { json: null, user_preferences: req.cookies })
+ }
+ if(json) {
+ try {
+ let prefs = JSON.parse(json)
+ let subbed_subreddits_is_set = false
+ for(var setting in prefs) {
+ if(prefs.hasOwnProperty(setting)) {
+ res.cookie(setting, prefs[setting], { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true })
+ if(setting === 'subbed_subreddits')
+ subbed_subreddits_is_set = true
+ }
+ }
+ if(!subbed_subreddits_is_set)
+ res.clearCookie('subbed_subreddits')
+ return res.redirect('/preferences')
+ } catch(e) {
+ console.error(`Error setting imported preferences to the cookies. Key: ${key}.`, error)
+ }
+ } else {
+ return res.redirect('/preferences')
+ }
+ })
+ })
app.get('/privacy', (req, res, next) => {
return res.render('privacypolicy', { user_preferences: req.cookies })
@@ -1001,14 +1037,25 @@ module.exports = (app, redis, fetch, RedditAPI) => {
})
})
- app.get('/user/:user', (req, res, next) => {
- res.redirect(`/u/${req.params.user}`)
+ app.get('/user/:user/:kind?', (req, res, next) => {
+ let kind = ''
+ if(req.params.kind)
+ kind = `/${req.params.kind}`
+ let q = ''
+ if(req.query.sort)
+ q += `?sort=${req.query.sort}&`
+ if(req.query.t)
+ q += `t=${req.query.t}`
+
+ res.redirect(`/u/${req.params.user}${kind}${q}`)
})
- app.get('/u/:user/:sort?', (req, res, next) => {
+ app.get('/u/:user/:kind?', (req, res, next) => {
let user = req.params.user
let after = req.query.after
let before = req.query.before
+ let post_type = req.params.kind
+ let kind = post_type
let user_data = {}
let api_req = req.query.api
let api_type = req.query.type
@@ -1029,6 +1076,19 @@ module.exports = (app, redis, fetch, RedditAPI) => {
if(before) {
d = `&before=${before}`
}
+
+ post_type = `/${post_type}`
+ switch(post_type) {
+ case '/comments':
+ kind = 't1'
+ break;
+ case '/submitted':
+ kind = 't3'
+ break;
+ default:
+ post_type = ''
+ kind = ''
+ }
let sortby = req.query.sort
let past = req.query.t
@@ -1059,7 +1119,7 @@ module.exports = (app, redis, fetch, RedditAPI) => {
}
}
- let key = `${user}:${after}:${before}:sort:${sortby}:past:${past}`
+ let key = `${user}:${after}:${before}:sort:${sortby}:past:${past}:post_type:${post_type}`
redis.get(key, (error, json) => {
if(error) {
console.error(`Error getting the user ${key} key from redis.`, error)
@@ -1068,10 +1128,10 @@ module.exports = (app, redis, fetch, RedditAPI) => {
if(json) {
console.log(`Got user ${user} key from redis.`);
(async () => {
- if(api_req) {
+ if(api_req) {
return handleTedditApiUser(json, req, res, 'redis', api_type, api_target, user, after, before)
} else {
- let processed_json = await processJsonUser(json, false, after, before, req.cookies)
+ let processed_json = await processJsonUser(json, false, after, before, req.cookies, kind, post_type)
return res.render('user', {
data: processed_json,
sortby: sortby,
@@ -1093,10 +1153,14 @@ module.exports = (app, redis, fetch, RedditAPI) => {
.then(json => {
user_data.about = json
let url = ''
- if(config.use_reddit_oauth)
- url = `https://oauth.reddit.com/user/${user}/overview?limit=26${d}&sort=${sortby}&t=${past}`
- else
- url = `https://reddit.com/user/${user}.json?limit=26${d}&sort=${sortby}&t=${past}`
+ if(config.use_reddit_oauth) {
+ let endpoint = '/overview'
+ if(post_type !== '')
+ endpoint = post_type
+ url = `https://oauth.reddit.com/user/${user}${post_type}?limit=26${d}&sort=${sortby}&t=${past}`
+ } else {
+ url = `https://reddit.com/user/${user}${post_type}.json?limit=26${d}&sort=${sortby}&t=${past}`
+ }
fetch(encodeURI(url), redditApiGETHeaders())
.then(result => {
if(result.status === 200) {
@@ -1109,10 +1173,10 @@ module.exports = (app, redis, fetch, RedditAPI) => {
return res.render('index', { post: null, user_preferences: req.cookies })
} else {
(async () => {
- if(api_req) {
+ if(api_req) {
return handleTedditApiUser(user_data, req, res, 'online', api_type, api_target, user, after, before)
} else {
- let processed_json = await processJsonUser(user_data, true, after, before, req.cookies)
+ let processed_json = await processJsonUser(user_data, true, after, before, req.cookies, kind, post_type)
return res.render('user', {
data: processed_json,
sortby: sortby,
@@ -1144,7 +1208,7 @@ module.exports = (app, redis, fetch, RedditAPI) => {
})
} else {
if(result.status === 404) {
- console.log('404 – User not found')
+ console.log('404 – User not found')
} else {
console.error(`Something went wrong while fetching data from Reddit. ${result.status} – ${result.statusText}`)
console.error(config.reddit_api_error_text)
@@ -1196,6 +1260,19 @@ module.exports = (app, redis, fetch, RedditAPI) => {
return res.redirect('/preferences')
})
+
+ app.post('/export_prefs', (req, res, next) => {
+ let r = `${(Math.random().toString(36)+'00000000000000000').slice(2, 10+2).toUpperCase()}`
+ let key = `prefs_key:${r}`
+ redis.set(key, JSON.stringify(req.cookies), (error) => {
+ if(error) {
+ console.error(`Error saving preferences to redis.`, error)
+ return res.redirect('/preferences')
+ } else {
+ return res.render('preferences', { user_preferences: req.cookies, instance_config: config, preferences_key: r })
+ }
+ })
+ })
app.post('/r/:subreddit/comments/:id/:snippet', (req, res, next) => {
/* morechildren route */
diff --git a/static/css/sepia.css b/static/css/sepia.css
index c8dbac5..928bf4b 100644
--- a/static/css/sepia.css
+++ b/static/css/sepia.css
@@ -223,4 +223,11 @@ body.sepia .comments > form button {
body.sepia #sidebar {
width: 100%;
}
+ body.sepia #links .link .entry details[open] .preview {
+ width: calc(100vw - 34px);
+ margin-left: 3px;
+ }
+ body.sepia #links .link .entry .selftext {
+ width: calc(100vw - 70px);
+ }
}
diff --git a/static/css/styles.css b/static/css/styles.css
index f25b966..ca23764 100644
--- a/static/css/styles.css
+++ b/static/css/styles.css
@@ -150,6 +150,12 @@ form .setting {
margin: 10px 0;
width: 100%;
}
+#export-form {
+ margin: 60px 0px 0px 0px;
+}
+#export-form input {
+ margin: 10px 0px 10px 0px;
+}
.container .content small.notice {
padding-top: 20px;
padding-bottom: 5px;
@@ -183,6 +189,9 @@ header a {
header a.main {
margin-left: 12px;
}
+header h3.username {
+ margin: 0px 15px 0px 0px;
+}
header .bottom {
float: left;
overflow: hidden;
@@ -497,6 +506,9 @@ footer a {
#links .link .entry details .line:first-child {
margin-top: 0;
}
+#links .link .entry details.preview-container img {
+ max-height: 600px !important;
+}
/* COMMENTS */
.comment {
font-size: 0.83rem;
@@ -1542,4 +1554,13 @@ code {
.explore#links .link .entry {
width: calc(100% - 20px);
}
+ #links .link .entry details[open] .preview {
+ width: 100vw;
+ transform: translateX(-150px);
+ }
+ #links .link .entry .selftext {
+ width: calc(100vw - 40px);
+ transform: translateX(-150px);
+ margin-left: 10px;
+ }
}
diff --git a/views/about.pug b/views/about.pug
index aba4be0..1bf9c17 100644
--- a/views/about.pug
+++ b/views/about.pug
@@ -3,7 +3,7 @@ html
head
title about - teddit
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
.container
.content
diff --git a/views/includes/head.pug b/views/includes/head.pug
index 3d497b3..19c0854 100644
--- a/views/includes/head.pug
+++ b/views/includes/head.pug
@@ -1,3 +1,5 @@
+if(user_preferences.theme === 'auto')
+ link(rel="stylesheet", type="text/css", href="/css/dark.css", media="(prefers-color-scheme: dark)")
if(user_preferences.theme === 'dark')
link(rel="stylesheet", type="text/css", href="/css/dark.css")
if(user_preferences.theme === 'sepia')
diff --git a/views/index.pug b/views/index.pug
index a5c5db1..0768728 100644
--- a/views/index.pug
+++ b/views/index.pug
@@ -3,7 +3,7 @@ html
head
title teddit
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
if json === null
h2 error
diff --git a/views/post.pug b/views/post.pug
index e88fa74..83f723c 100644
--- a/views/post.pug
+++ b/views/post.pug
@@ -3,7 +3,7 @@ html
head
title #{cleanTitle(post.title)} : #{subreddit}
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
if post === null
h1 Error occured
diff --git a/views/preferences.pug b/views/preferences.pug
index 9d173a7..1ec6fe2 100644
--- a/views/preferences.pug
+++ b/views/preferences.pug
@@ -3,7 +3,7 @@ html
head
title preferences - teddit
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
.container
.content
@@ -14,17 +14,25 @@ html
label(for="theme") Theme:
select(id="theme", name="theme")
if(!user_preferences.theme || user_preferences.theme == '')
+ option(value="auto") Auto
option(value="", selected="selected") White
option(value="dark") Dark
option(value="sepia") Sepia
if(user_preferences.theme === 'dark')
+ option(value="auto") Auto
option(value="") White
option(value="dark", selected="selected") Dark
option(value="sepia") Sepia
if(user_preferences.theme === 'sepia')
+ option(value="auto") Auto
option(value="") White
option(value="dark") Dark
option(value="sepia", selected="selected") Sepia
+ if(user_preferences.theme === 'auto')
+ option(value="auto", selected="selected") Auto
+ option(value="") White
+ option(value="dark") Dark
+ option(value="sepia") Sepia
.setting
label(for="flairs") Show flairs:
if(!user_preferences.flairs || user_preferences.flairs == 'true')
@@ -59,4 +67,27 @@ html
small(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
+ form(action="/export_prefs", method="POST", id="export-form")
+ if preferences_key
+ details(open)
+ summary
+ span Export preferences
+ .setting
+ small By exporting your preferences you can transfer your subscribed subreddits and preferences to another device. Or you could create a bookmark if you tend to delete your cookies frequently.
+ br
+ input(type="submit", value="Export preferences")
+ if preferences_key
+ - var protocol = 'http'
+ if instance_config.https_enabled === true
+ - var protocol = 'https'
+ p Visit this URL to import your preferences:
+ a(href=`${protocol}://${instance_config.domain}/import_prefs/${preferences_key}`) #{protocol}://#{instance_config.domain}/import_prefs/#{preferences_key}
+ else
+ details
+ summary
+ span Export preferences
+ .setting
+ small By exporting your preferences you can transfer your subscribed subreddits and preferences to another device. Or you could create a bookmark if you tend to delete your cookies frequently.
+ br
+ input(type="submit", value="Export preferences")
include includes/footer.pug
diff --git a/views/privacypolicy.pug b/views/privacypolicy.pug
index 803a70c..20bf2f9 100644
--- a/views/privacypolicy.pug
+++ b/views/privacypolicy.pug
@@ -3,7 +3,7 @@ html
head
title privacy policy - teddit
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
.container
.content
diff --git a/views/search.pug b/views/search.pug
index 1abaab7..bb2ace4 100644
--- a/views/search.pug
+++ b/views/search.pug
@@ -6,7 +6,7 @@ html
else
title search results for #{q}
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
#search.sr.search-page
form(action="/r/" + subreddit + "/search", method="GET")
diff --git a/views/subreddit.pug b/views/subreddit.pug
index 08b81bc..683b003 100644
--- a/views/subreddit.pug
+++ b/views/subreddit.pug
@@ -3,7 +3,7 @@ html
head
title /r/#{subreddit}
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
-
let show_nsfw_warning = false;
diff --git a/views/subreddits_explore.pug b/views/subreddits_explore.pug
index fe23008..9823a1f 100644
--- a/views/subreddits_explore.pug
+++ b/views/subreddits_explore.pug
@@ -3,7 +3,7 @@ html
head
title subreddits - explore
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
if json === null
h1 Error occured
diff --git a/views/user.pug b/views/user.pug
index 6a6c65e..284193b 100644
--- a/views/user.pug
+++ b/views/user.pug
@@ -3,13 +3,24 @@ html
head
title overview for #{data.username}
include includes/head.pug
- body(class=""+ user_preferences.theme +"")
+ body(class=""+ (user_preferences.theme === 'auto' ? 'dark' : user_preferences.theme) + "")
include includes/topbar.pug
if user === null
h1 Error occured
p #{JSON.stringify(error_data)}
else
#user
+ header
+ .bottom
+ a(href="/u/" + data.username + "")
+ h3.username user: #{data.username}
+ ul.tabmenu
+ li(class=!data.post_type || data.post_type == '' ? 'active' : '')
+ a(href="/u/" + data.username) overview
+ li(class=data.post_type === '/comments' ? 'active' : '')
+ a(href="/u/" + data.username + "/comments") comments
+ li(class=data.post_type === '/submitted' ? 'active' : '')
+ a(href="/u/" + data.username + "/submitted") submitted
#links
details
summary
@@ -23,13 +34,13 @@ html
span sorted by: controversial
ul
li(class=!sortby || sortby == 'new' ? 'active' : '')
- a(href="/u/" + data.username) new
+ a(href="/u/" + data.username + data.post_type) new
li(class=sortby === 'hot' ? 'active' : '')
- a(href="/u/" + data.username + "?sort=hot") hot
+ a(href="/u/" + data.username + data.post_type + "?sort=hot") hot
li(class=sortby === 'top' ? 'active' : '')
- a(href="/u/" + data.username + "?sort=top&t=" + past + "") top
+ a(href="/u/" + data.username + data.post_type + "?sort=top&t=" + past + "") top
li(class=sortby === 'controversial' ? 'active' : '')
- a(href="/u/" + data.username + "?sort=controversial&t=" + past + "") controversial
+ a(href="/u/" + data.username + data.post_type + "?sort=controversial&t=" + past + "") controversial
if sortby === 'top' || sortby === 'controversial'
details
summary
@@ -132,7 +143,7 @@ html
if data.before || data.after
p view more:
if data.before && !data.user_front
- a(href="/u/" + data.username + "?before=" + data.before + "") ‹ prev
+ a(href="/u/" + data.username + data.post_type + "?before=" + data.before + "") ‹ prev
if data.after
- a(href="/u/" + data.username + "?after=" + data.after + "") next ›
+ a(href="/u/" + data.username + data.post_type + "?after=" + data.after + "") next ›
include includes/footer.pug