From 15f20f329120339e8c82672d860965af052bbe2a Mon Sep 17 00:00:00 2001 From: Akshay Date: Thu, 29 Aug 2024 22:14:18 +0100 Subject: init --- .gitignore | 179 ++++++++++++++++++++++ build.js | 9 ++ flake.lock | 27 ++++ flake.nix | 99 +++++++++++++ package.json | 17 +++ public/styles.css | 92 ++++++++++++ readme.txt | 1 + src/geddit.js | 392 +++++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 17 +++ src/routes/index.js | 35 +++++ src/views/comment.pug | 18 +++ src/views/comments.pug | 25 ++++ src/views/index.pug | 19 +++ src/views/post.pug | 21 +++ src/views/utils.pug | 3 + 15 files changed, 954 insertions(+) create mode 100644 .gitignore create mode 100644 build.js create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 package.json create mode 100644 public/styles.css create mode 100644 readme.txt create mode 100644 src/geddit.js create mode 100644 src/index.js create mode 100644 src/routes/index.js create mode 100644 src/views/comment.pug create mode 100644 src/views/comments.pug create mode 100644 src/views/index.pug create mode 100644 src/views/post.pug create mode 100644 src/views/utils.pug diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da80e40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,179 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +reference +bun.lockb +result diff --git a/build.js b/build.js new file mode 100644 index 0000000..a16c54b --- /dev/null +++ b/build.js @@ -0,0 +1,9 @@ +import { execSync } from "bun"; + +try { + // Precompile Pug templates in the `src` directory to the `dist` directory + execSync("pug src -o dist"); + console.log("Pug templates compiled successfully."); +} catch (error) { + console.error("Failed to compile Pug templates:", error); +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..fa59981 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1724748588, + "narHash": "sha256-NlpGA4+AIf1dKNq76ps90rxowlFXUsV9x7vK/mN37JM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "a6292e34000dc93d43bccf78338770c1c5ec8a99", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..917e2e0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,99 @@ +{ + inputs = { + + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + + }; + + outputs = + { self + , nixpkgs + }: + let + supportedSystems = [ "x86_64-linux" ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + nixpkgsFor = forAllSystems (system: + import nixpkgs { + inherit system; + overlays = [ self.overlays.default ]; + }); + + in + { + overlays.default = final: prev: { + node_modules = with final; stdenv.mkDerivation { + pname = "readit-node-modules"; + version = "0.0.1"; + impureEnvVars = lib.fetchers.proxyImpureEnvVars + ++ [ "GIT_PROXY_COMMAND" "SOCKS_SERVER" ]; + src = ./.; + nativeBuildInputs = [ bun ]; + buildInputs = [ nodejs-slim_latest ]; + dontConfigure = true; + dontFixup = true; + buildPhase = '' + bun install --no-progress --frozen-lockfile + ''; + installPhase = '' + mkdir -p $out/node_modules + cp -R ./node_modules/* $out/node_modules + ls -la $out/node_modules + ''; + outputHash = "sha256-qFYgRIarDChHQu0ZrUKd/Y61gxaagMWpf2h9xizwGv4="; + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + }; + readit = with final; stdenv.mkDerivation { + pname = "readit"; + version = "0.0.1"; + src = ./.; + nativeBuildInputs = [ makeBinaryWrapper ]; + buildInputs = [ bun ]; + + buildPhase = '' + runHook preBuild + + + runHook postBuild + ''; + + dontFixup = true; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin + + # cp app.js $out/app.js + cp -R ./* $out + + # bun is referenced naked in the package.json generated script + # makeBinaryWrapper ${bun}/bin/bun $out/bin/$pname \ + # --add-flags "run --prefer-offline --no-install $out/app.js" + + makeBinaryWrapper ${bun}/bin/bun $out/bin/$pname \ + --prefix PATH : ${lib.makeBinPath [ bun ]} \ + --add-flags "run --prefer-offline --no-install $out/src/index.js" + + ''; + }; + }; + + devShell = forAllSystems (system: + let + pkgs = nixpkgsFor."${system}"; + in + pkgs.mkShell { + nativeBuildInputs = [ + pkgs.bun + ]; + RUST_BACKTRACE = 1; + }); + + packages = forAllSystems(system: { + inherit (nixpkgsFor."${system}") readit node_modules; + }); + }; +} + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..3e68c4c --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "scarlet", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "pug-cli": "^1.0.0-alpha6" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "express": "^4.19.2", + "pug": "^3.0.3", + "timeago.js": "^4.0.2" + } +} \ No newline at end of file diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..1a541de --- /dev/null +++ b/public/styles.css @@ -0,0 +1,92 @@ +main { + display: flex; + flex-direction: column; + gap: 1rem; + align-items: center; +} + +.post, .comments-container, .header { + padding: 0.3rem; + flex: 1 1 95%; + font-size: 1rem; + width: 95%; +} + +@media (min-width: 768px) { + .post, .comments-container, .header { + flex: 1 1 65%; + width: 65%; + } +} + +@media (min-width: 1080px) { + .post, .comments-container, .header { + flex: 1 1 50%; + width: 50%; + } +} + +.comments-container, .self-text { + text-align: justify; +} + +.comment, .more { + width: 100%; + border-left: 1px dashed; + padding: 10px 0px 10px 24px; + box-sizing: border-box; +} + +.more { + margin-bottom: 0px; + font-size: 0.7rem; + color: #777; +} + +.first { + border-left: none; + padding-left: 0; + margin: 0; + margin-top: 12px; +} + +.post-container { + align-self: stretch; + display: flex; +} + +.post-text { + flex-direction: column; + align-items: stretch; + justify-content: space-between; +} + +.media-preview { + padding-left: 10px; + margin-left: auto; +} + +.post-media { + max-width: 100%; +} + +.title-container,.info-container { + flex: 1; + margin-top: 10px; + margin-bottom: 10px; +} + +.info-container { + color: #777; + font-size: 0.8rem; + display: flex; + align-items: center; +} + +.info-item { + margin-right: 10px; +} + +hr { + border 1px solid #000; +} diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..f041728 --- /dev/null +++ b/readme.txt @@ -0,0 +1 @@ +nix build .#readit diff --git a/src/geddit.js b/src/geddit.js new file mode 100644 index 0000000..825c3a9 --- /dev/null +++ b/src/geddit.js @@ -0,0 +1,392 @@ +class Geddit { + constructor() { + this.host = "https://www.reddit.com"; + this.parameters = { + limit: 25, + include_over_18: true, + } + this.search_params = { + limit: 25, + include_over_18: true, + type: "sr,link,user", + } + } + + async getSubmissions(sort = null, subreddit = null, options = {}) { + let params = { + limit: 25, + include_over_18: true, + } + + sort = sort ? sort : "hot"; + subreddit = subreddit ? "/r/" + subreddit : ""; + + return await fetch(this.host + subreddit + `/${sort}.json?` + new URLSearchParams(Object.assign(params, options))) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + posts: data.children + })) + .catch(err => null); + + } + + async getDomainHot(domain, options = this.parameters) { + return await fetch(this.host + "/domain/" + domain + "/hot.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + posts: data.children + })) + .catch(err => null); + } + + async getDomainBest(domain, options = this.parameters) { + return await fetch(this.host + "/domain/" + domain + "/best.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + posts: data.children + })) + .catch(err => null); + } + + async getDomainTop(domain, options = this.parameters) { + return await fetch(this.host + "/domain/" + domain + "/top.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + posts: data.children + })) + .catch(err => null); + } + + async getDomainNew(domain, options = this.parameters) { + return await fetch(this.host + "/domain/" + domain + "/new.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + posts: data.children + })) + .catch(err => null); + } + + async getDomainRising(domain, options = this.parameters) { + return await fetch(this.host + "/domain/" + domain + "/rising.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + posts: data.children + })) + .catch(err => null); + } + + async getDomainControversial(domain, options = this.parameters) { + return await fetch(this.host + "/domain/" + domain + "/controversial.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + posts: data.children + })) + .catch(err => null); + } + + async getSubreddit(subreddit) { + return await fetch(this.host + "/r/" + subreddit + "/about.json") + .then(res => res.json()) + .then(json => json.data) + .catch(err => null); + } + + async getSubredditRules(subreddit) { + return await fetch(this.host + "/r/" + subreddit + "/about/rules.json") + .then(res => res.json()) + .then(json => json.data) + .catch(err => null); + } + + async getSubredditModerators(subreddit) { + return await fetch(this.host + "/r/" + subreddit + "/about/moderators.json") + .then(res => res.json()) + .then(json => json.data) + .then(data = ({ + users: data.children + })) + .catch(err => null); + } + + async getSubredditWikiPages(subreddit) { + return await fetch(this.host + "/r/" + subreddit + "/wiki/pages.json") + .then(res => res.json()) + .then(json => json.data) + .catch(err => null); + } + + async getSubredditWikiPage(subreddit, page) { + return await fetch(this.host + "/r/" + subreddit + "/wiki/" + page + ".json") + .then(res => res.json()) + .then(json => json.data) + .catch(err => null); + } + + async getSubredditWikiPageRevisions(subreddit, page) { + return await fetch(this.host + "/r/" + subreddit + "/wiki/revisions" + page + ".json") + .then(res => res.json()) + .then(json => json.data.children) + .catch(err => null); + } + + async getPopularSubreddits(options = this.parameters) { + return await fetch(this.host + "/subreddits/popular.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + subreddits: data.children + })) + .catch(err => null); + } + + async getNewSubreddits(options = this.parameters) { + return await fetch(this.host + "/subreddits/new.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + subreddits: data.children + })) + .catch(err => null); + } + + async getPremiumSubreddits(options = this.parameters) { + return await fetch(this.host + "/subreddits/premium.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + subreddits: data.children + })) + .catch(err => null); + } + + async getDefaultSubreddits(options = this.parameters) { + return await fetch(this.host + "/subreddits/default.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + subreddits: data.children + })) + .catch(err => null); + } + + async getPopularUsers(options = this.parameters) { + return await fetch(this.host + "/users/popular.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + users: data.children + })) + .catch(err => null); + } + + async getNewUsers(options = this.parameters) { + return await fetch(this.host + "/users/new.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + users: data.children + })) + .catch(err => null); + } + + async searchSubmissions(query, options = {}) { + options.q = query; + options.type = "link"; + + let params = { + limit: 25, + include_over_18: true + } + + console.log(this.host + "/search.json?" + new URLSearchParams(Object.assign(params, options))); + + return await fetch(this.host + "/search.json?" + new URLSearchParams(Object.assign(params, options))) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + items: data.children + })) + .catch(err => null); + } + + async searchSubreddits(query, options = {}) { + options.q = query; + + let params = { + limit: 25, + include_over_18: true + } + + return await fetch(this.host + "/subreddits/search.json?" + new URLSearchParams(Object.assign(params, options))) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + items: data.children + })) + .catch(err => null); + } + + async searchUsers(query, options = {}) { + options.q = query; + + let params = { + limit: 25, + include_over_18: true + } + + return await fetch(this.host + "/users/search.json?" + new URLSearchParams(Object.assign(params, options))) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + items: data.children + })) + .catch(err => null); + } + + async searchAll(query, subreddit = null, options = {}) { + options.q = query; + subreddit = subreddit ? "/r/" + subreddit : ""; + + let params = { + limit: 25, + include_over_18: true, + type: "sr,link,user", + } + + return await fetch(this.host + subreddit + "/search.json?" + new URLSearchParams(Object.assign(params, options))) + .then(res => res.json()) + .then(json => Array.isArray(json) ? ({ + after: json[1].data.after, + items: json[0].data.children.concat(json[1].data.children) + }) : ({ + after: json.data.after, + items: json.data.children + })) + .catch(err => null); + } + + async getSubmission(id) { + return await fetch(this.host + "/by_id/" + id + ".json") + .then(res => res.json()) + .then(json => json.data.children[0].data) + .catch(err => null); + } + + async getSubmissionComments(id, options = this.parameters) { + return await fetch(this.host + "/comments/" + id + ".json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => ({ + submission: json[0].data.children[0], + comments: json[1].data.children + })) + .catch(err => null); + } + + async getSubredditComments(subreddit, options = this.parameters) { + return await fetch(this.host + "/r/" + subreddit + "/comments.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data.children) + .catch(err => null); + } + + async getUser(username) { + return await fetch(this.host + "/user/" + username + "/about.json") + .then(res => res.json()) + .then(json => json.data) + .catch(err => null); + } + + async getUserOverview(username, options = this.parameters) { + return await fetch(this.host + "/user/" + username + "/overview.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + items: data.children + })) + .catch(err => null); + } + + async getUserComments(username, options = this.parameters) { + return await fetch(this.host + "/user/" + username + "/comments.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + items: data.children + })) + .catch(err => null); + } + + async getUserSubmissions(username, options = this.parameters) { + return await fetch(this.host + "/user/" + username + "/submitted.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data) + .then(data => ({ + after: data.after, + items: data.children + })) + .catch(err => null); + } + + async getLiveThread(id) { + return await fetch(this.host + "/live/" + id + "/about.json") + .then(res => res.json()) + .then(json => json.data) + .catch(err => null); + } + + async getLiveThreadUpdates(id, options = this.parameters) { + return await fetch(this.host + "/live/" + id + ".json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data.children) + .catch(err => null); + } + + + async getLiveThreadContributors(id, options = this.parameters) { + return await fetch(this.host + "/live/" + id + "/contributors.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data.children) + .catch(err => null); + } + + async getLiveThreadDiscussions(id, options = this.parameters) { + return await fetch(this.host + "/live/" + id + "/discussions.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data.children) + .catch(err => null); + } + + async getLiveThreadsNow(options = this.parameters) { + return await fetch(this.host + "/live/happening_now.json?" + new URLSearchParams(options)) + .then(res => res.json()) + .then(json => json.data.children) + .catch(err => null); + } +} + +export { Geddit } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..bfe31f4 --- /dev/null +++ b/src/index.js @@ -0,0 +1,17 @@ +const express = require('express'); +const path = require('path'); +const routes = require('./routes/index'); +const geddit = require('./geddit.js'); + +const app = express(); + +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); + +app.use(express.static('public')); +app.use('/', routes); + +const server = app.listen(3000, () => { + console.log(`started on ${server.address().port}`); +}); + diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..6cf5403 --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,35 @@ +const express = require('express'); +const router = express.Router(); +const geddit = require('../geddit.js'); +const G = new geddit.Geddit(); +const fs = require('fs/promises'); + + +// GET / +router.get('/', async (req, res) => { + res.redirect("/r/all") +}); + +// GET /r/:id +router.get('/r/:subreddit', async (req, res) => { + var subreddit = req.params.subreddit; + + var postsReq = G.getSubmissions(`r/${subreddit}`); + var aboutReq = G.getSubreddit(`${subreddit}`); + + var [posts, about] = await Promise.all([postsReq, aboutReq]); + res.render('index', { subreddit, posts, about }); +}); + +// GET /comments/:id +router.get('/comments/:id', async (req, res) => { + var id = req.params.id; + + response = await G.getSubmissionComments(id); + var post = response.submission.data; + var comments = response.comments; + + res.render('comments', { post, comments }); +}); + +module.exports = router; diff --git a/src/views/comment.pug b/src/views/comment.pug new file mode 100644 index 0000000..24e1a9b --- /dev/null +++ b/src/views/comment.pug @@ -0,0 +1,18 @@ +include utils +mixin comment(com, isfirst) + - var data = com.data + - var kind = com.kind + if kind == "more" + div.more #{data.count} more comments + else + div(class=`comment ${isfirst?'first':''}`) + div.comment-body !{data.body} + div.info-container + div.info-item by u/#{data.author} + div.info-item ↑ #{fmtnum(data.ups)} + div.replies + if data.replies + if data.replies.data + if data.replies.data.children + each reply in data.replies.data.children + +comment(reply,false) diff --git a/src/views/comments.pug b/src/views/comments.pug new file mode 100644 index 0000000..f7964a3 --- /dev/null +++ b/src/views/comments.pug @@ -0,0 +1,25 @@ +doctype html +html + head + meta(charset='UTF-8') + title reddit + link(rel='stylesheet', href='/styles.css') + body + main#content + div.header + a(href=`/r/${post.subreddit}`) + h4 ← r/#{post.subreddit} + h2 #{post.title} + if post.post_hint == 'image' + img(src=post.url).post-media + else if post.post_hint == 'hosted:video' + video(src=post.url).post-media + p.self-text !{post.selftext} + hr + + div.comments-container + each child in comments + include comment + +comment(child, true) + + script(src='https://unpkg.com/htmx.org@1.9.10') diff --git a/src/views/index.pug b/src/views/index.pug new file mode 100644 index 0000000..d017570 --- /dev/null +++ b/src/views/index.pug @@ -0,0 +1,19 @@ +doctype html +html + head + meta(charset='UTF-8') + title reddit + link(rel='stylesheet', href='/styles.css') + body + main#content + div.header + a(href=`/r/#{subreddit}`) + h1 r/#{subreddit} + if about + p #{about.public_description} + + each child in posts.posts + include post + +post(child.data) + + script(src='https://unpkg.com/htmx.org@1.9.10') diff --git a/src/views/post.pug b/src/views/post.pug new file mode 100644 index 0000000..77ef3f5 --- /dev/null +++ b/src/views/post.pug @@ -0,0 +1,21 @@ +include utils +mixin post(p) + article.post + div.post-container + div.post-text + div.title-container !{p.title} + div.info-container + div.info-item by u/#{p.author} + div.info-item ↑ #{fmtnum(p.ups)} + div.info-item #{p.domain} + div.info-item + a(href=`/r/${p.subreddit}`) r/#{p.subreddit} + div.info-item + a(href=`/comments/${p.id}`) #{fmtnum (p.num_comments)} #{fmttxt(p.num_comments, 'comment')} + div.media-preview + if p.post_hint == "image" || p.post_hint == "link" + if p.thumbnail && p.thumbnail != "self" || p.thumbnail != "default" + a(href=p.url) + img(src=p.thumbnail width='100px') + else if p.post_hint == "hosted:video" + video(src=p.secure_media.reddit_video.scrubber_media_url width='100px') diff --git a/src/views/utils.pug b/src/views/utils.pug new file mode 100644 index 0000000..f3f61bb --- /dev/null +++ b/src/views/utils.pug @@ -0,0 +1,3 @@ +- var fmtnum = (n)=>n>=1000?(n/1000).toFixed(1)+'k':n; +- var fmttxt = (n,t)=>`${t}${n==1?'':'s'}` + -- cgit v1.2.3