First commit: /a/, /gallery/, images, gifv
This commit is contained in:
commit
7c2e53c6e4
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
5995
package-lock.json
generated
Normal file
5995
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
package.json
Normal file
37
package.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "imgur-proxy",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"typings": "dist/index",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"build": "npx tsc",
|
||||||
|
"watch": "npx tsc --watch",
|
||||||
|
"test": "npx mocha -r ts-node/register test/**/*.test.ts",
|
||||||
|
"dev:tsc": "tsc --watch -p .",
|
||||||
|
"dev:serve": "nodemon -e js -w dist dist/index.js",
|
||||||
|
"dev": "run-p dev:*"
|
||||||
|
},
|
||||||
|
"author": "3np",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/hapi__hapi": "^20.0.9",
|
||||||
|
"@types/hapi__inert": "^5.2.3",
|
||||||
|
"@types/hapi__vision": "^5.5.3",
|
||||||
|
"@types/node": "^16.10.3",
|
||||||
|
"@types/pug": "^2.0.5",
|
||||||
|
"nodemon": "^2.0.13",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"typescript": "^4.4.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/hapi": "^20.2.0",
|
||||||
|
"@hapi/inert": "^6.0.4",
|
||||||
|
"@hapi/vision": "^6.1.0",
|
||||||
|
"cheerio": "^1.0.0-rc.10",
|
||||||
|
"got": "^11.8.2",
|
||||||
|
"hpagent": "^0.1.2",
|
||||||
|
"pug": "^3.0.2"
|
||||||
|
}
|
||||||
|
}
|
BIN
samples/.a_DfEsrAB.swp
Normal file
BIN
samples/.a_DfEsrAB.swp
Normal file
Binary file not shown.
1
samples/a_DfEsrAB
Normal file
1
samples/a_DfEsrAB
Normal file
File diff suppressed because one or more lines are too long
1
samples/comments_g1bk7CB
Normal file
1
samples/comments_g1bk7CB
Normal file
File diff suppressed because one or more lines are too long
1
samples/gallery_g1bk7CB
Normal file
1
samples/gallery_g1bk7CB
Normal file
File diff suppressed because one or more lines are too long
8
src/config.ts
Normal file
8
src/config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
port: process.env.RIMGU_PORT || 8080,
|
||||||
|
host: process.env.RIMGU_HOST || 'localhost',
|
||||||
|
address: process.env.RIMGU_ADDRESS || '127.0.0.1',
|
||||||
|
http_proxy: process.env.RIMGU_HTTP_PROXY || null,
|
||||||
|
https_proxy: process.env.RIMGU_HTTPS_PROXY || null,
|
||||||
|
imgur_client_id: process.env.RIMGU_IMGUR_CLIENT_ID || null,
|
||||||
|
};
|
68
src/fetchers.ts
Normal file
68
src/fetchers.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import cheerio from 'cheerio';
|
||||||
|
import got, { Response } from 'got';
|
||||||
|
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent';
|
||||||
|
import { globalAgent as httpGlobalAgent } from 'http';
|
||||||
|
import { globalAgent as httpsGlobalAgent } from 'https';
|
||||||
|
|
||||||
|
import CONFIG from './config';
|
||||||
|
|
||||||
|
const GALLERY_JSON_REGEX = /window\.postDataJSON=(".*")$/;
|
||||||
|
|
||||||
|
const agent = {
|
||||||
|
http: CONFIG.http_proxy
|
||||||
|
? new HttpProxyAgent({
|
||||||
|
keepAlive: true,
|
||||||
|
keepAliveMsecs: 1000,
|
||||||
|
maxSockets: 256,
|
||||||
|
maxFreeSockets: 256,
|
||||||
|
scheduling: 'lifo',
|
||||||
|
proxy: CONFIG.http_proxy,
|
||||||
|
})
|
||||||
|
: httpGlobalAgent,
|
||||||
|
https: CONFIG.https_proxy
|
||||||
|
? new HttpsProxyAgent({
|
||||||
|
keepAlive: true,
|
||||||
|
keepAliveMsecs: 1000,
|
||||||
|
maxSockets: 256,
|
||||||
|
maxFreeSockets: 256,
|
||||||
|
scheduling: 'lifo',
|
||||||
|
proxy: CONFIG.https_proxy,
|
||||||
|
})
|
||||||
|
: httpsGlobalAgent
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchComments = async (galleryID: string): Promise<Comment[]> => {
|
||||||
|
// https://api.imgur.com/comment/v1/comments?client_id=${CLIENT_ID}%5Bpost%5D=eq%3Ag1bk7CB&include=account%2Cadconfig&per_page=30&sort=best
|
||||||
|
const response = await got(`https://api.imgur.com/comment/v1/comments?client_id=${CONFIG.imgur_client_id}&filter%5Bpost%5D=eq%3A${galleryID}&include=account%2Cadconfig&per_page=30&sort=best`);
|
||||||
|
return JSON.parse(response.body).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchGallery = async (galleryID: string): Promise<Gallery> => {
|
||||||
|
// https://imgur.com/gallery/g1bk7CB
|
||||||
|
const response = await got(`https://imgur.com/gallery/${galleryID}`, { agent });
|
||||||
|
const $ = cheerio.load(response.body);
|
||||||
|
const postDataScript = $('head script:first-of-type').html();
|
||||||
|
if (!postDataScript) {
|
||||||
|
throw new Error('Could not find gallery data');
|
||||||
|
}
|
||||||
|
const postDataMatches = postDataScript.match(GALLERY_JSON_REGEX);
|
||||||
|
if (!postDataMatches || postDataMatches.length < 2) {
|
||||||
|
throw new Error('Could not parse gallery data');
|
||||||
|
}
|
||||||
|
const postData = JSON.parse(JSON.parse(postDataMatches[1]));
|
||||||
|
return postData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchAlbumURL = async (albumID: string): Promise<string> => {
|
||||||
|
// https://imgur.com/a/DfEsrAB
|
||||||
|
const response = await got(`https://imgur.com/a/${albumID}`, { agent });
|
||||||
|
const $ = cheerio.load(response.body);
|
||||||
|
const url = $('head meta[property="og:image"]').attr('content')?.replace(/\/\?.*$/, '');
|
||||||
|
if (!url) {
|
||||||
|
throw new Error('Could not read image url');
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchMedia = async (filename: string): Promise<Response<string>> =>
|
||||||
|
await got(`https://i.imgur.com/${filename}`, { agent });
|
45
src/handlers.ts
Normal file
45
src/handlers.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import Hapi = require('@hapi/hapi');
|
||||||
|
import '@hapi/vision';
|
||||||
|
import { fetchAlbumURL, fetchComments, fetchGallery, fetchMedia } from './fetchers';
|
||||||
|
import * as util from './util';
|
||||||
|
|
||||||
|
export const handleMedia = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||||
|
const {
|
||||||
|
baseName,
|
||||||
|
extension,
|
||||||
|
} = request.params;
|
||||||
|
const result = await fetchMedia(`${baseName}.${extension}`);
|
||||||
|
const response = h.response(result.rawBody)
|
||||||
|
.header('Content-Type', result.headers["content-type"] || `image/${extension}`);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleAlbum = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||||
|
// https://imgur.com/a/DfEsrAB
|
||||||
|
const url = await fetchAlbumURL(request.params.albumID);
|
||||||
|
return h.view('album', {
|
||||||
|
url,
|
||||||
|
util,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleUser = (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||||
|
// https://imgur.com/user/MomBotNumber5
|
||||||
|
throw new Error('not implemented');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleTag = (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||||
|
// https://imgur.com/t/funny
|
||||||
|
throw new Error('not implemented');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleGallery = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||||
|
const galleryID = request.params.galleryID;
|
||||||
|
const gallery = await fetchGallery(galleryID);
|
||||||
|
const comments = await fetchComments(galleryID);
|
||||||
|
return h.view('gallery', {
|
||||||
|
...gallery,
|
||||||
|
comments,
|
||||||
|
util,
|
||||||
|
});
|
||||||
|
};
|
74
src/index.ts
Normal file
74
src/index.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import Hapi = require('@hapi/hapi');
|
||||||
|
import Path = require('path');
|
||||||
|
import { handleAlbum, handleGallery, handleMedia, handleTag, handleUser } from './handlers';
|
||||||
|
|
||||||
|
import CONFIG from './config';
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
const server = Hapi.server({
|
||||||
|
port: CONFIG.port,
|
||||||
|
host: CONFIG.host,
|
||||||
|
address: CONFIG.address,
|
||||||
|
routes: {
|
||||||
|
files: {
|
||||||
|
relativeTo: Path.join(__dirname, 'static')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await server.register(require('@hapi/vision'));
|
||||||
|
await server.register(require('@hapi/inert'));
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/css/{param*}',
|
||||||
|
handler: ({
|
||||||
|
directory: {
|
||||||
|
path: Path.join(__dirname, 'static/css')
|
||||||
|
}
|
||||||
|
} as any)
|
||||||
|
});
|
||||||
|
server.views({
|
||||||
|
engines: {
|
||||||
|
pug: require('pug')
|
||||||
|
},
|
||||||
|
relativeTo: __dirname,
|
||||||
|
path: 'templates',
|
||||||
|
});
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/{baseName}.{extension}',
|
||||||
|
handler: handleMedia,
|
||||||
|
});
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/a/{albumID?}',
|
||||||
|
handler: handleAlbum,
|
||||||
|
});
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/t/{tagID?}',
|
||||||
|
handler: handleTag,
|
||||||
|
});
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/user/{userID?}',
|
||||||
|
handler: handleUser,
|
||||||
|
});
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/gallery/{galleryID}',
|
||||||
|
handler: handleGallery,
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.start();
|
||||||
|
console.log('Server running on %s', server.info.uri);
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
init();
|
77
src/types/index.d.ts
vendored
Normal file
77
src/types/index.d.ts
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
interface Account {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
avatar_url: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Gallery {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
account: Account;
|
||||||
|
media: Media[];
|
||||||
|
tags: Tag[];
|
||||||
|
cover: Media;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MediaMimeType = 'image/jpeg' | 'image/png' | 'image/gif';
|
||||||
|
type MediaType = 'image';
|
||||||
|
type MediaExt = 'jpeg' | 'png' | 'gif';
|
||||||
|
|
||||||
|
interface Tag {
|
||||||
|
tag: string;
|
||||||
|
display: string;
|
||||||
|
background_id: string;
|
||||||
|
accent: string;
|
||||||
|
is_promoted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Media {
|
||||||
|
id: string;
|
||||||
|
account_id: number;
|
||||||
|
mime_type: MediaMimeType;
|
||||||
|
type: MediaType;
|
||||||
|
name: string;
|
||||||
|
basename: string;
|
||||||
|
url: string;
|
||||||
|
ext: MediaExt;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
size: number;
|
||||||
|
metadata: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
is_animated: boolean;
|
||||||
|
is_looping: boolean;
|
||||||
|
duration: number;
|
||||||
|
has_sound: boolean;
|
||||||
|
},
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MediaPlatform = 'ios' | 'android' | 'api' | 'web';
|
||||||
|
interface Comment {
|
||||||
|
id: number;
|
||||||
|
parent_id: number;
|
||||||
|
comment: string;
|
||||||
|
account_id: number;
|
||||||
|
post_id: string;
|
||||||
|
upvote_count: number;
|
||||||
|
downvote_count: number;
|
||||||
|
point_count: number;
|
||||||
|
vote: null; // ?
|
||||||
|
platform_id: number;
|
||||||
|
platform: MediaPlatform;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: "2021-10-01T00:08:51Z",
|
||||||
|
deleted_at: null,
|
||||||
|
next: null; //?
|
||||||
|
comments: Comment[];
|
||||||
|
account: {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
11
src/util.ts
Normal file
11
src/util.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export const proxyURL = (url: string): string =>
|
||||||
|
url.replace(/^https?:\/\/[^.]*\.imgur.com\//, '/');
|
||||||
|
|
||||||
|
export const linkify = (content: string) =>
|
||||||
|
content.replace(
|
||||||
|
/https?:\/\/[^.]*\.imgur.com\/([\/_a-zA-Z0-9-]+)\.gifv/g,
|
||||||
|
'<video src="/$1.mp4" class="commentVideo commentObject" loop="" autoplay=""></video>'
|
||||||
|
).replace(
|
||||||
|
/https?:\/\/[^.]*\.imgur.com\/([\/_a-zA-Z0-9-]+\.[a-z0-9A-Z]{2,6})/g,
|
||||||
|
'<a href="/$1" target="_blank"><img class="commentImage commentObject" src="/$1" loading="lazy" /></a>'
|
||||||
|
);
|
4
static/css/custom.css
Normal file
4
static/css/custom.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
img.album-img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
139
static/css/styles.css
Normal file
139
static/css/styles.css
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
.UserAvatar {
|
||||||
|
display: block;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TagPill {
|
||||||
|
box-shadow: 0 5px 5px rgba(0,0,0,.25);
|
||||||
|
border-radius: 54px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: .02em;
|
||||||
|
color: #eff1f4;
|
||||||
|
text-shadow: 0 1px 4px #000;
|
||||||
|
padding: 8px 30px;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: Proxima Nova Bold,Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||||
|
transition: box-shadow .2s ease-out;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-avatar-bar .avatar span {
|
||||||
|
display: block;
|
||||||
|
background-color: grey;
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-byLine .author-name {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: #01b96b;
|
||||||
|
font-family: Proxima Nova Bold,Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-avatar-bar .avatar {
|
||||||
|
margin: 2px 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-byLine {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
color: #b4b9c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-avatar-bar {
|
||||||
|
width: 24px;
|
||||||
|
margin-right: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-byLine .Meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, ::after, ::before {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-replies {
|
||||||
|
padding-left: 32px;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-body .commentObject {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
max-height: 100px;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-body {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 150%;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
color: #eff1f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-actions .points {
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-actions .actions-btn {
|
||||||
|
color: #b4b9c2;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vote {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-actions .actions-btn.vote-btn {
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-actions .actions-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #b4b9c2;
|
||||||
|
outline: none;
|
||||||
|
/* cursor: pointer; */
|
||||||
|
padding: 5px;
|
||||||
|
position: relative;
|
||||||
|
min-height: 19px;
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 26px;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GalleryComment-actions {
|
||||||
|
display: flex;
|
||||||
|
margin: 4px 0 8px -5px;
|
||||||
|
align-items: center;
|
||||||
|
color: #b4b9c2;
|
||||||
|
font-family: Proxima Nova Bold,Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
}
|
6
templates/album.pug
Normal file
6
templates/album.pug
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
html
|
||||||
|
head
|
||||||
|
title imgur-proxy
|
||||||
|
include includes/head.pug
|
||||||
|
body
|
||||||
|
img(src=util.proxyURL(url), alt='' class='album-img')
|
71
templates/gallery.pug
Normal file
71
templates/gallery.pug
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
mixin commentbox(comment)
|
||||||
|
div(class='GalleryComment')
|
||||||
|
div(class='GalleryComment-wrapper')
|
||||||
|
div(class='GalleryComment-content')
|
||||||
|
div(class='GalleryComment-byLine')
|
||||||
|
div(class='Meta')
|
||||||
|
div(class='GalleryComment-avatar-bar')
|
||||||
|
div(class='avatar')
|
||||||
|
a(title='View profile of '+comment.account.username, href='/user/'+comment.account.username)
|
||||||
|
span(title=comment.account.username, style='background-image: url("' + util.proxyURL(comment.account.avatar) + '");')
|
||||||
|
a(class='author-name', title='View profile of '+comment.account.username, href='/user/'+comment.account.username) #{comment.account.username}
|
||||||
|
span(class="date", title=comment.created_at)
|
||||||
|
span(class="delimiter") •
|
||||||
|
span #{comment.created_at} via <a class="platform bold" href="/apps">#{comment.platform}</a>
|
||||||
|
div(class='GalleryComment-body')
|
||||||
|
span(class='Linkify')
|
||||||
|
| !{util.linkify(comment.comment)}
|
||||||
|
div(class='GalleryComment-actions')
|
||||||
|
div(class='vote-btn upvote actions-btn' title='Upvotes')
|
||||||
|
div(class='Vote Vote-up')
|
||||||
|
svg(width='16', height='16', viewBox='0 0 16 16', fill='none', xmlns='http://www.w3.org/2000/svg')
|
||||||
|
title Upvotes
|
||||||
|
| <path fill="none" stroke="#B4B9C2" stroke-width="2" fill-rule="evenodd" clip-rule="evenodd" d="M7.197 2.524a1.2 1.2 0 011.606 0c.521.46 1.302 1.182 2.363 2.243a29.617 29.617 0 012.423 2.722c.339.435.025 1.028-.526 1.028h-2.397v4.147c0 .524-.306.982-.823 1.064-.417.066-1.014.122-1.843.122s-1.427-.056-1.843-.122c-.517-.082-.824-.54-.824-1.064V8.517H2.937c-.552 0-.865-.593-.527-1.028.52-.669 1.32-1.62 2.423-2.722a52.996 52.996 0 012.364-2.243z"></path>
|
||||||
|
.points + #{comment.upvote_count}
|
||||||
|
div(class='vote-btn down actions-btn' title='Downvotes')
|
||||||
|
div(class='Vote Vote-down')
|
||||||
|
svg(width='16', height='16', viewBox='0 0 16 16', fill='none', xmlns='http://www.w3.org/2000/svg')
|
||||||
|
title Downvotes
|
||||||
|
| <path fill="none" stroke="#B4B9C2" stroke-width="2" fill-rule="evenodd" clip-rule="evenodd" d="M8.803 13.476a1.2 1.2 0 01-1.606 0 53.03 53.03 0 01-2.364-2.243 29.613 29.613 0 01-2.422-2.722c-.339-.435-.025-1.028.526-1.028h2.397V3.336c0-.524.306-.982.823-1.064A11.874 11.874 0 018 2.15c.829 0 1.427.056 1.843.122.517.082.824.54.824 1.064v4.147h2.396c.552 0 .865.593.527 1.028-.52.669-1.32 1.62-2.423 2.722a53.038 53.038 0 01-2.364 2.243z"></path>
|
||||||
|
.points - #{comment.downvote_count}
|
||||||
|
.points = #{comment.point_count}
|
||||||
|
div(class='GalleryComment-replies')
|
||||||
|
each reply in comment.comments
|
||||||
|
+commentbox(reply)
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title imgur-proxy
|
||||||
|
include includes/head.pug
|
||||||
|
body
|
||||||
|
div(class='Gallery-Content')
|
||||||
|
div(class='Gallery-Header')
|
||||||
|
div(class='Gallery-Title')
|
||||||
|
span #{title}
|
||||||
|
div(class='Gallery-Byline')
|
||||||
|
a(class='author-link' title='View profile of '+account.username, href='/user/'+account.username)
|
||||||
|
span(class='UserAvatar', title=account.username, style='background-image: url("' + util.proxyURL(account.avatar_url) + '");')
|
||||||
|
div(class='Info-Wrapper')
|
||||||
|
div(class='Info')
|
||||||
|
a(class='author-name' title='View profile of '+account.username, href='/user/'+account.username) #{account.username}
|
||||||
|
div(class='Meta')
|
||||||
|
span #{view_count} Views
|
||||||
|
span(class='delimiter') •
|
||||||
|
span(title=created_at) #{created_at}
|
||||||
|
div(class='Gallery-ContentWrapper')
|
||||||
|
div(class='Gallery-Content--media')
|
||||||
|
div(class='imageContainer')
|
||||||
|
img(src=util.proxyURL(cover.url))
|
||||||
|
div(class='Gallery-Content--tags')
|
||||||
|
each tag in tags
|
||||||
|
a(class='TagPill'
|
||||||
|
style='background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)) repeat scroll 0% 0%, rgba(0, 0, 0, 0) url("/' + tag.background_id + '_d.jpg?maxwidth=200&fidelity=grand") repeat scroll 0% 0%;'
|
||||||
|
href='/t/'+tag.tag) #{tag.tag}
|
||||||
|
div(class='CommentsList')
|
||||||
|
div(class='CommentsList-headline')
|
||||||
|
div(class='CommentsList-headline--counter')
|
||||||
|
span #{comments.length} Comments
|
||||||
|
div
|
||||||
|
div(class='CommentsList-comments')
|
||||||
|
div(class='CommentsList-comments--container')
|
||||||
|
each comment in comments
|
||||||
|
+commentbox(comment)
|
2
templates/includes/head.pug
Normal file
2
templates/includes/head.pug
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
link(rel="stylesheet", type="text/css", href="/css/styles.css")
|
||||||
|
link(rel="stylesheet", type="text/css", href="/css/custom.css")
|
17
tsconfig.json
Executable file
17
tsconfig.json
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2019",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": false,
|
||||||
|
"outDir": "dist",
|
||||||
|
"typeRoots": [
|
||||||
|
"src/types/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user