~fkfd/fkfd.me

ecd64fe8ededa4992f5df2985fa9274bd65be038 — Frederick Yin 4 years ago 0530092 master
Debloat

Remove all frontend JavaScript
Remove example images
Remove unused CSS's and handlebars templates
Update quotes
31 files changed, 100 insertions(+), 635 deletions(-)

M index.js
D metagen.js
D metamod.js
M package-lock.json
D public/.directory
D public/comics/1/meta.json
D public/comics/1/roche-limit.kra
D public/comics/1/roche-limit.png
D public/comics/2/laws.kra
D public/comics/2/laws.png
D public/comics/2/meta.json
D public/comics/3/meta.json
D public/comics/3/sorry-but.kra
D public/comics/3/sorry-but.png
D public/css/admin.css
D public/css/auth.css
D public/css/comic.css
M public/css/xkcd.css
D public/img/fkfd-integration.png
D public/js/admin.js
D public/js/comic.js
D public/js/lib/axios.min.js
M quotes.json
D views/admin.handlebars
M views/archive.handlebars
D views/auth.handlebars
M views/comic.handlebars
D views/edit.handlebars
M views/layouts/xkcd.handlebars
D views/search.handlebars
D views/token.handlebars
M index.js => index.js +4 -7
@@ 24,11 24,6 @@ const fs = require('fs-extra')
// config file containing passwd, tokens, etc
const config = fs.readJSONSync('./config.json')

app.get('/about', (req, res) => {
    // res.render('about', {});
    res.redirect('https://blog.fkfd.me/pages/about')
})

app.get('/archive', (req, res) => {
    const total = fs.readdirSync('./public/comics/').length,
        comic = []


@@ 37,7 32,7 @@ app.get('/archive', (req, res) => {
        comic.push({
            id: id + 1,
            title: meta.name,
            date: `${meta.month}/${meta.date}/${meta.year}`
            date: `${meta.year}/${meta.month}/${meta.date}`
        })
    }



@@ 84,12 79,14 @@ app.get('/*', (req, res) => {
            date,
            author,
            src,
            ogp_image: src.replace('.webp', '.png'),
            hover,
            prev: id === 1 ? '1' : id - 1,
            next: id === latest ? '' : id + 1,
            note,
            quote,
            rand
            rand,
	    id
        })
    } else {
        res.render('forbidden', {

D metagen.js => metagen.js +0 -111
@@ 1,111 0,0 @@
const chalk = require('chalk')
const inquirer = require('inquirer')
const fs = require('fs-extra')

const date = new Date()
const id = fs.readdirSync('./public/comics/').length + 1

const generateSafeTitle = original => {
    const safe = original
        .toLowerCase()
        .replace("'", '')
        .replace(/\W/g, '-')
        .replace(/[,.?!]/g, '')
        .replace(/[_/]/g, '-')
        .replace(' & ', '-and-')
    return safe
}

const questions = [
    {
        type: 'input',
        name: 'name',
        message: 'Title:'
    },
    {
        type: 'number',
        name: 'year',
        message: 'Year:',
        default: date.getFullYear()
    },
    {
        type: 'number',
        name: 'month',
        message: 'Month:',
        default: date.getMonth() + 1, // date.getMonth() is zero-indexed
        validate: input => {
            // is a valid month
            if (input >= 1 && input <= 12) return true
            return 'Invalid month'
        }
    },
    {
        type: 'number',
        name: 'date',
        message: 'Date:',
        default: date.getDate(),
        validate: input => {
            if (input >= 1 && input <= 31) return true
            return 'Invalid date'
        }
    },
    {
        type: 'input',
        name: 'author',
        message: 'Author:',
        default: Object.keys(fs.readJSONSync('./config.json').users)[0]
    },
    // file a bug report if you happen to live on Mars
    {
        type: 'input',
        name: 'hover',
        message: 'Hover:'
    },
    {
        type: 'text',
        name: 'tags',
        message: 'Tags(separate with comma): '
    },
    {
        type: 'input',
        name: 'image',
        message: 'Image filename:',
        default: '${SAFE_TITLE}.png'
    }
]

console.info(chalk.bold.bgGreen(`## You are uploading comic #${id} ##`))
inquirer.prompt(questions).then(answers => {
    const safeTitle = generateSafeTitle(answers.name)
    answers.image = answers.image.replace('${SAFE_TITLE}', safeTitle)
    answers.tags = answers.tags.split(', ')
    console.info(chalk.bold.bgGreen('Please review metadata: \n'))
    Object.keys(answers).forEach(field => {
        console.info(chalk.yellow(field + ': ') + answers[field])
    })
    inquirer
        .prompt([
            {
                type: 'confirm',
                name: 'confirm',
                message: 'Confirm? '
            }
        ])
        .then(fb => {
            if (fb.confirm) {
                fs.mkdirp(`./public/comics/${id}`).then(() => {
                    fs.writeJSON(`./public/comics/${id}/meta.json`, {
                        id,
                        name: answers.name,
                        year: answers.year,
                        month: answers.month,
                        date: answers.date,
                        author: answers.author,
                        hover: answers.hover,
                        tags: answers.tags,
                        image: answers.image
                    })
                })
            }
        })
})

D metamod.js => metamod.js +0 -130
@@ 1,130 0,0 @@
const chalk = require('chalk')
const inquirer = require('inquirer')

const fs = require('fs-extra')
const comicDirs = fs.readdirSync('./public/comics/')

const retrieveMetadata = () => {
    let comicMeta = []
    comicDirs.forEach(dir => {
        const meta = fs.readJSONSync(`./public/comics/${dir}/meta.json`)
        comicMeta[meta.id] = meta
    })
    // list of all meta
    return comicMeta
}

const comicMetaFields = [
    'id',
    'name',
    'year',
    'month',
    'date',
    'author',
    'hover',
    'image',
    'tags'
]

const main = () => {
    console.info(chalk.bold.bgGreen.white('## Welcome to fkfdMetaMod ##'))
    const metaList = retrieveMetadata()
    console.info(chalk.bold.yellow('Your most recent comics:'))
    metaList
        .reverse()
        .slice(0, 10)
        .forEach(meta => {
            console.info(`#${meta.id}: ${meta.name}`)
        })
    metaList.reverse()

    inquirer
        .prompt({
            type: 'number',
            name: 'num',
            message: 'Select comic',
            validate: n => {
                if (n >= 1 && n <= metaList.length - 1) return true
            },
            default: metaList.length - 1
        })
        .then(comicFb => {
            const name = metaList[comicFb.num].name,
                id = comicFb.num
            console.info(chalk.green(`You are editing #${id}: ${name}`))
            console.info(chalk.green(`Metadata for #${id}:`))

            comicMetaFields.forEach(field => {
                console.info(`${chalk.bold(field)}: ${metaList[id][field]}`)
            })
            inquirer
                .prompt({
                    type: 'list',
                    name: 'field',
                    message: 'Select field to modify',
                    choices: comicMetaFields
                })
                .then(fieldFb => {
                    const field = fieldFb.field
                    console.info(chalk.green(`Modifying ${field} of #${id}: `))
                    if (field === 'tags') {
                        let tags = metaList[id].tags ? metaList[id].tags : []
                        console.log(updateTags(tags))
                    }
                })
        })
}

const updateTags = tags => {
    console.info(
        chalk.green(`You have set ${tags.length} tag(s) for this comic \n`),
        chalk.green('Overview: ')
    )
    console.info(chalk.bold('## Start of tags ##'))
    tags.forEach((tag, index) => {
        console.info(`Tag #${index}: ${tag}`)
    })
    console.info(chalk.bold('## End of tags ##'))

    console.info(chalk.bold.yellow('Instructions:'))
    console.info(chalk.yellow('a - add'))
    console.info(chalk.yellow('d - delete'))
    console.info(chalk.yellow('m - modify'))
    console.info(chalk.yellow('q - quit'))

    inquirer
        .prompt({
            type: 'text',
            name: 'command',
            message: '> ',
            validate: input => {
                if (
                    input === 'a' ||
                    input === 'd' ||
                    input === 'm' ||
                    input === 'q'
                )
                    return true
            }
        })
        .then(commandFb => {
            switch (commandFb.command) {
                case 'a':
                    inquirer
                        .prompt({
                            type: 'text',
                            name: 'newTag',
                            message: 'Your new tag: '
                        })
                        .then(newTagFb => {
                            if (!tags.includes(newTagFb.newTag)) {
                                tags.push(newTag)
                            }
                        })
            }
        })

    return tags
}

main()

M package-lock.json => package-lock.json +23 -23
@@ 24,9 24,9 @@
      }
    },
    "acorn": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz",
      "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==",
      "version": "6.4.1",
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
      "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
      "dev": true
    },
    "acorn-dynamic-import": {


@@ 446,9 446,9 @@
      }
    },
    "commander": {
      "version": "2.20.0",
      "resolved": "https://r.cnpmjs.org/commander/download/commander-2.20.0.tgz",
      "integrity": "sha1-1YuytcHuj4ew00ACfp6U4iLFpCI=",
      "version": "2.20.3",
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
      "optional": true
    },
    "concat-map": {


@@ 886,9 886,9 @@
      "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
    },
    "handlebars": {
      "version": "4.1.2",
      "resolved": "https://r.cnpmjs.org/handlebars/download/handlebars-4.1.2.tgz",
      "integrity": "sha1-trN8HO0DBrIh4JT8eso+wjsTG2c=",
      "version": "4.7.2",
      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.2.tgz",
      "integrity": "sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw==",
      "requires": {
        "neo-async": "^2.6.0",
        "optimist": "^0.6.1",


@@ 898,8 898,8 @@
      "dependencies": {
        "source-map": {
          "version": "0.6.1",
          "resolved": "https://r.cnpmjs.org/source-map/download/source-map-0.6.1.tgz",
          "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM="
          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
        }
      }
    },


@@ 1127,9 1127,9 @@
      }
    },
    "lodash": {
      "version": "4.17.11",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
      "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
      "version": "4.17.15",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
    },
    "lodash.memoize": {
      "version": "3.0.4",


@@ 1283,9 1283,9 @@
      "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
    },
    "neo-async": {
      "version": "2.6.0",
      "resolved": "https://r.cnpmjs.org/neo-async/download/neo-async-2.6.0.tgz",
      "integrity": "sha1-udFeTXHGdikIZUtRg+04t1M0CDU="
      "version": "2.6.1",
      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
      "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
    },
    "object-keys": {
      "version": "1.1.0",


@@ 1875,19 1875,19 @@
      "dev": true
    },
    "uglify-js": {
      "version": "3.5.5",
      "resolved": "https://r.cnpmjs.org/uglify-js/download/uglify-js-3.5.5.tgz",
      "integrity": "sha1-nFqq86dYb79VnfMfptO8obO6fps=",
      "version": "3.7.6",
      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.6.tgz",
      "integrity": "sha512-yYqjArOYSxvqeeiYH2VGjZOqq6SVmhxzaPjJC1W2F9e+bqvFL9QXQ2osQuKUFjM2hGjKG2YclQnRKWQSt/nOTQ==",
      "optional": true,
      "requires": {
        "commander": "~2.20.0",
        "commander": "~2.20.3",
        "source-map": "~0.6.1"
      },
      "dependencies": {
        "source-map": {
          "version": "0.6.1",
          "resolved": "https://r.cnpmjs.org/source-map/download/source-map-0.6.1.tgz",
          "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
          "optional": true
        }
      }

D public/.directory => public/.directory +0 -6
@@ 1,6 0,0 @@
[Dolphin]
Timestamp=2019,2,13,13,19,6
Version=4

[Settings]
HiddenFilesShown=true

D public/comics/1/meta.json => public/comics/1/meta.json +0 -10
@@ 1,10 0,0 @@
 {
     "id": 1,
     "name": "Roche Limit",
     "year": 2019,
     "month": 2,
     "date": 6,
     "author": "fakefred",
     "hover": "I tried losing weight, but it didn't help.",
     "image": "roche-limit.png"
 }

D public/comics/1/roche-limit.kra => public/comics/1/roche-limit.kra +0 -0
D public/comics/1/roche-limit.png => public/comics/1/roche-limit.png +0 -0
D public/comics/2/laws.kra => public/comics/2/laws.kra +0 -0
D public/comics/2/laws.png => public/comics/2/laws.png +0 -0
D public/comics/2/meta.json => public/comics/2/meta.json +0 -10
@@ 1,10 0,0 @@
{
    "id": 2,
    "name": "Laws",
    "year": 2019,
    "month": 2,
    "date": 6,
    "author": "fakefred",
    "hover": "Meanwhile, Leibniz's Public Relation Team smirks.",
    "image": "laws.png"
}

D public/comics/3/meta.json => public/comics/3/meta.json +0 -10
@@ 1,10 0,0 @@
{
    "id": 3,
    "name": "Sorry, But",
    "year": 2019,
    "month": 2,
    "date": 7,
    "author": "fakefred",
    "hover": "'I'm afraid of falling in the mainstream, so I-' 'You use Arch?' 'No, I use Windows Vista.'",
    "image": "sorry-but.png"
}

D public/comics/3/sorry-but.kra => public/comics/3/sorry-but.kra +0 -0
D public/comics/3/sorry-but.png => public/comics/3/sorry-but.png +0 -0
D public/css/admin.css => public/css/admin.css +0 -32
@@ 1,32 0,0 @@
#input-title {
    width: 16em;
    font-size: 1.5em;
}

#month {
    width: 3em;
}

#date {
    width: 3em;
}

#year {
    width: 5em;
}

#author {
    width: 10em;
}

.wide {
    width: 60%;
    height: 1.4em;
    font-size: 1.2em;
}

#upload {
    text-align: center;
    position: relative;
    margin-bottom: 24px;
}
\ No newline at end of file

D public/css/auth.css => public/css/auth.css +0 -3
@@ 1,3 0,0 @@
form {
    margin: 24px;
}
\ No newline at end of file

D public/css/comic.css => public/css/comic.css +0 -74
@@ 1,74 0,0 @@
body {
    font-family: 'Noto Sans', 'Droid Sans', sans-serif;
    position: relative;
    font-size: 1em;
    left: 50%;
    width: 780px;
    margin-left: -390px;
    background-color: #96A8C8;
}

.small {
    font-size: 0.8em;
    color: #444;
}

.box {
    display: block;
    border-width: 1.5px;
    border-style: solid;
    border-radius: 12px;
    background-color: #fff;
}

header {
    position: relative;
    width: 780px;
}

h1 {
    font-weight: 250;
}

#heading {
    margin-left: 24px;
}

#banner {
    display: inline;
}

#container {
    text-align: center;
    position: relative;
    margin-top: 15px;
    width: 100%;
}

button {
    background-color: #68A;
    color: white;
    border: solid white 1px;
    font-size: 1.2em;
}

.nav {
    position: relative;
    margin: 24px;
}

#content {
    text-align: center;
}

#note {
    margin-left: 24px;
}

#comic {
    display: block;
    position: relative;
    max-width: 90%;
    margin-left: auto;
    margin-right: auto;
}
\ No newline at end of file

M public/css/xkcd.css => public/css/xkcd.css +46 -6
@@ 5,7 5,13 @@ body {
    left: 50%;
    max-width: 960px;
    margin-left: -480px;
    background-color: #96A8C8;
    background-color: #96a8c8;
}

@media (prefers-color-scheme: dark) {
    body {
        background-color: #19202b;
    }
}

a {


@@ 13,32 19,62 @@ a {
    color: #566888;
}

@media (prefers-color-scheme: dark) {
    a {
        color: #7b91be;
    }
}

.small {
    font-size: 0.8em;
    color: #444;
}

@media (prefers-color-scheme: dark) {
    .small {
        color: #ddd;
    }
}

.box {
    display: block;
    border-width: 1.5px;
    border-color: black;
    border-style: solid;
    border-radius: 12px;
    background-color: #fff;
}

@media (prefers-color-scheme: dark) {
    .box {
        background-color: #202836;
        color: #ddd;
    }
}

.here {
    color: black;
}

@media (prefers-color-scheme: dark) {
    .here {
        color: #ddd;
    }
}

.center {
    text-align: center;
}

h1, h2 {
    font-weight: 250;
h1,
h2 {
    font-weight: 300;
}

h3, h4, h5, h6 {
h3,
h4,
h5,
h6 {
    font-weight: 500;
}



@@ 62,12 98,16 @@ h3, h4, h5, h6 {
}

button {
    background-color: #68A;
    background-color: #68a;
    color: white;
    border: solid white 1px;
    font-size: 1.2em;
}

@media (prefers-color-scheme: dark) {

}

.nav {
    position: relative;
    margin: 24px;


@@ 87,4 127,4 @@ button {
    max-width: 90%;
    margin-left: auto;
    margin-right: auto;
}
\ No newline at end of file
}

D public/img/fkfd-integration.png => public/img/fkfd-integration.png +0 -0
D public/js/admin.js => public/js/admin.js +0 -20
@@ 1,20 0,0 @@
let date = new Date();
document.getElementById('month').value = date.getMonth() + 1;
document.getElementById('date').value = date.getDate();
document.getElementById('year') .value = date.getFullYear();

setInterval(() => {
    if (
        document.getElementById('input-title').value !== '' &&
        document.getElementById('month').value !== '' &&
        document.getElementById('date').value !== '' &&
        document.getElementById('year').value !== '' &&
        document.getElementById('author').value !== '' &&
        document.getElementById('disp-url').value !== '' &&
        document.getElementById('disp-fn').value !== '' &&
        document.getElementById('hover').value !== '' &&
        document.getElementById('password').value !== ''
    ) {
        document.getElementById('submit').type = 'submit';
    }
}, 2000);
\ No newline at end of file

D public/js/comic.js => public/js/comic.js +0 -86
@@ 1,86 0,0 @@
const page_title = document.getElementsByTagName('title')[0],
    title = document.getElementById('title'),
    info = document.getElementById('info'),
    comic = document.getElementById('comic')

const MONTH_HASH = [
    'Wtf',
    'Jan',
    'Feb',
    'Mar',
    'Apr',
    'May',
    'June',
    'July',
    'Aug',
    'Sept',
    'Oct',
    'Nov',
    'Dec'
]

let current = latest

function render(id) {
    comic.src = '/img/loading.png'
    axios
        .get('/request', {
            params: {
                type: 'comic',
                id
            }
        })
        .then(res => {
            if (res.error) {
                handleError(res, error)
            } else {
                let data = res.data
                console.log(data)
                page_title.innerText = title.innerHTML = data.name
                info.innerHTML = `${MONTH_HASH[data.month]} ${data.date} ${
                    data.year
                } <i>by</i> ${data.author}`
                comic.src = `/comics/${data.id}/${data.image}`
                comic.title = data.hover
                current = id
            }
        })
}

function handleError(err) {
    alert(err)
}

function navigate(param) {
    if (param === 'next' && current < latest) {
        render(++current)
    } else if (param === 'prev' && current > 1) {
        render(--current)
    } else if (param === 'random') {
        let randComic = current
        while (randComic === current) {
            randComic = Math.ceil(Math.random() * latest)
        }
        render(randComic)
    } else {
        // do nothing
    }
}

function go() {
    let id = parseInt(document.getElementById('goto').value)
    if (id <= latest && id > 0) {
        render(id)
    }
}

// init
let id = window.location.hash
if (!id) {
    render(latest)
} else if (/\d+/.test(id)) {
    id = parseInt(id.slice(1))
    if (id > 0 && id <= latest) {
        render(id)
    }
}

D public/js/lib/axios.min.js => public/js/lib/axios.min.js +0 -2
@@ 1,2 0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(5),u=n(6),a=r(u);a.Axios=s,a.create=function(e){return r(o.merge(u,e))},a.Cancel=n(23),a.CancelToken=n(24),a.isCancel=n(20),a.all=function(e){return Promise.all(e)},a.spread=n(25),e.exports=a,e.exports.default=a},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"[object ArrayBuffer]"===R.call(e)}function i(e){return"undefined"!=typeof FormData&&e instanceof FormData}function s(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function u(e){return"string"==typeof e}function a(e){return"number"==typeof e}function c(e){return"undefined"==typeof e}function f(e){return null!==e&&"object"==typeof e}function p(e){return"[object Date]"===R.call(e)}function d(e){return"[object File]"===R.call(e)}function l(e){return"[object Blob]"===R.call(e)}function h(e){return"[object Function]"===R.call(e)}function m(e){return f(e)&&h(e.pipe)}function y(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function w(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function g(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function v(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&t.call(null,e[i],i,e)}function x(){function e(e,n){"object"==typeof t[n]&&"object"==typeof e?t[n]=x(t[n],e):t[n]=e}for(var t={},n=0,r=arguments.length;n<r;n++)v(arguments[n],e);return t}function b(e,t,n){return v(t,function(t,r){n&&"function"==typeof t?e[r]=E(t,n):e[r]=t}),e}var E=n(3),C=n(4),R=Object.prototype.toString;e.exports={isArray:r,isArrayBuffer:o,isBuffer:C,isFormData:i,isArrayBufferView:s,isString:u,isNumber:a,isObject:f,isUndefined:c,isDate:p,isFile:d,isBlob:l,isFunction:h,isStream:m,isURLSearchParams:y,isStandardBrowserEnv:g,forEach:v,merge:x,extend:b,trim:w}},function(e,t){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t){function n(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function r(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&n(e.slice(0,0))}
e.exports=function(e){return null!=e&&(n(e)||r(e)||!!e._isBuffer)}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new s,response:new s}}var o=n(6),i=n(2),s=n(17),u=n(18);r.prototype.request=function(e){"string"==typeof e&&(e=i.merge({url:arguments[0]},arguments[1])),e=i.merge(o,{method:"get"},this.defaults,e),e.method=e.method.toLowerCase();var t=[u,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},i.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(i.merge(n||{},{method:e,url:t}))}}),i.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(i.merge(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function o(){var e;return"undefined"!=typeof XMLHttpRequest?e=n(8):"undefined"!=typeof process&&(e=n(8)),e}var i=n(2),s=n(7),u={"Content-Type":"application/x-www-form-urlencoded"},a={adapter:o(),transformRequest:[function(e,t){return s(t,"Content-Type"),i.isFormData(e)||i.isArrayBuffer(e)||i.isBuffer(e)||i.isStream(e)||i.isFile(e)||i.isBlob(e)?e:i.isArrayBufferView(e)?e.buffer:i.isURLSearchParams(e)?(r(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):i.isObject(e)?(r(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300}};a.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){a.headers[e]={}}),i.forEach(["post","put","patch"],function(e){a.headers[e]=i.merge(u)}),e.exports=a},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(9),i=n(12),s=n(13),u=n(14),a=n(10),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(15);e.exports=function(e){return new Promise(function(t,f){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest,h="onreadystatechange",m=!1;if("undefined"==typeof window||!window.XDomainRequest||"withCredentials"in l||u(e.url)||(l=new window.XDomainRequest,h="onload",m=!0,l.onprogress=function(){},l.ontimeout=function(){}),e.auth){var y=e.auth.username||"",w=e.auth.password||"";d.Authorization="Basic "+c(y+":"+w)}if(l.open(e.method.toUpperCase(),i(e.url,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l[h]=function(){if(l&&(4===l.readyState||m)&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in l?s(l.getAllResponseHeaders()):null,r=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:r,status:1223===l.status?204:l.status,statusText:1223===l.status?"No Content":l.statusText,headers:n,config:e,request:l};o(t,f,i),l=null}},l.onerror=function(){f(a("Network Error",e,null,l)),l=null},l.ontimeout=function(){f(a("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=n(16),v=(e.withCredentials||u(e.url))&&e.xsrfCookieName?g.read(e.xsrfCookieName):void 0;v&&(d[e.xsrfHeaderName]=v)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),e.withCredentials&&(l.withCredentials=!0),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),f(e),l=null)}),void 0===p&&(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(10);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(11);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e}},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(o.isURLSearchParams(t))i=t.toString();else{var s=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),s.push(r(t)+"="+r(e))}))}),i=s.join("&")}return i&&(e+=(e.indexOf("?")===-1?"?":"&")+i),e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t){"use strict";function n(){this.message="String contains an invalid character"}function r(e){for(var t,r,i=String(e),s="",u=0,a=o;i.charAt(0|u)||(a="=",u%1);s+=a.charAt(63&t>>8-u%1*8)){if(r=i.charCodeAt(u+=.75),r>255)throw new n;t=t<<8|r}return s}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";n.prototype=new Error,n.prototype.code=5,n.prototype.name="InvalidCharacterError",e.exports=r},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var u=[];u.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&u.push("expires="+new Date(n).toGMTString()),r.isString(o)&&u.push("path="+o),r.isString(i)&&u.push("domain="+i),s===!0&&u.push("secure"),document.cookie=u.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";function r(){this.handlers=[]}var o=n(2);r.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},r.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},r.prototype.forEach=function(e){o.forEach(this.handlers,function(t){null!==t&&e(t)})},e.exports=r},function(e,t,n){"use strict";function r(e){e.cancelToken&&e.cancelToken.throwIfRequested()}var o=n(2),i=n(19),s=n(20),u=n(6),a=n(21),c=n(22);e.exports=function(e){r(e),e.baseURL&&!a(e.url)&&(e.url=c(e.baseURL,e.url)),e.headers=e.headers||{},e.data=i(e.data,e.headers,e.transformRequest),e.headers=o.merge(e.headers.common||{},e.headers[e.method]||{},e.headers||{}),o.forEach(["delete","get","head","post","put","patch","common"],function(t){delete e.headers[t]});var t=e.adapter||u.adapter;return t(e).then(function(t){return r(e),t.data=i(t.data,t.headers,e.transformResponse),t},function(t){return s(t)||(r(e),t&&t.response&&(t.response.data=i(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)})}},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t,n){return r.forEach(n,function(n){e=n(e,t)}),e}},function(e,t){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])});
\ No newline at end of file

M quotes.json => quotes.json +3 -2
@@ 4,6 4,7 @@
    "Report all NSFW content to /dev/null",
    "Right click - refresh does not help 90% of the time",
    "Did you know that most of modern websites do not require 'www.'?",
    "Congratulations for Krita's 4.2.7 release!",
    "Why not sarcasm?"
    "Congratulations for Krita's 4.2.9 release!",
    "Why not sarcasm?",
    "I never test before committing changes to the prod server."
]

D views/admin.handlebars => views/admin.handlebars +0 -32
@@ 1,32 0,0 @@
<header class="box">
    <div id="heading">
        <h1>FKFD</h1>
        <h2>Admin Console</h2>
        <p>Your Digital World Just Went Mobile<sup>&reg;&copy;&trade;</sup></p>
    </div>
</header>
<div id="container" class="box center">
    <div id="upload">
        <h2>Meta.json Editor (#{{id}})</h2>
        <h3>Upload Comic: <a href="https://drop.fkfd.me/#/comics">Link</a></h3>
        <form name="upload" action="/upload?token={{token}}" method="POST">
            <h3 id="title"><input type="text" id="input-title" name="title" placeholder="Comic Title"></h3>
            <span id="info">
                <input type="number" id="month" name="month" placeholder="M">/
                <input type="number" id="date"  name="date" placeholder="D">/
                <input type="number" id="year"  name="year" placeholder="Y"> 
                <br><br><i>by</i>
                <input type="text" id="author" name="author" placeholder="Author">
                <br><br>
            </span>
            
            <input class="wide" type="text" id="disp-fn" name="fn" value="${SAFE_TITLE}.png" placeholder="File Name"><br><br>
            <input class="wide" type="text" id="hover" name="hover" placeholder="Hover Text">
            
            <button id="submit" value="Submit">Submit</button>
        </form>
    </div>
</div>

<link rel="stylesheet" href="/css/admin.css">
<script src="/js/admin.js"></script>
\ No newline at end of file

M views/archive.handlebars => views/archive.handlebars +2 -2
@@ 1,6 1,6 @@
<header class="box">
    <div id="heading">
        <h1><a href="/">FKFD</a> | <a href="https://blog.fkfd.me/">Blog</a> | <a class="here" href="/archive">Archive</a> | <a href="https://blog.fkfd.me/pages/about">About</a></h1>
        <h1><a href="/">FKFD</a> | <a class="here" href="/archive">Archive</a></h1> 
        <p>Enjoy the list<sup>&reg;&copy;&trade;</sup></p>
    </div>
</header>


@@ 11,4 11,4 @@
            <a href="/{{id}}" title="{{date}}">{{id}}. {{title}}</a><br>
        {{/each}}
    </div>
</div>
\ No newline at end of file
</div>

D views/auth.handlebars => views/auth.handlebars +0 -17
@@ 1,17 0,0 @@
<header class="box">
    <div id="heading">
        <h1>FKFD</h1>
        <h2>Auth Interface</h2>
        <p>Security!<sup>&reg;&copy;&trade;</sup></p>
    </div>
</header>
<div id="container" class="box center">
    <form method="POST" action="/token">
        <input name="user" type="text" placeholder="Admin name"><br><br>
        <input name="pass" type="password" placeholder="Password"><br><br>
        <button type="submit">Obtain Token</button>
    </form>
</div>

<link rel="stylesheet" href="/css/auth.css">
<script src="/js/admin.js"></script>
\ No newline at end of file

M views/comic.handlebars => views/comic.handlebars +15 -13
@@ 1,8 1,7 @@
<header class="box">
    <div id="heading">
        <h1>
            <a class="here" href="/">FKFD</a> | <a href="https://blog.fkfd.me/">Blog</a> | <a
                href="/archive">Archive</a> | <a href="https://blog.fkfd.me/pages/about">About</a></h1>
            <a class="here" href="/">FKFD</a> | <a href="/archive">Archive</a></h1> 
        <p>{{quote}}<sup>&reg;&copy;&trade;</sup><br></p>
    </div>
</header>


@@ 16,7 15,7 @@

    <div id="content">
        <h3 id="title">{{title}}</h3>
        <h4 id="info">{{month}} {{date}} {{year}} <i>by</i> {{author}}</h4>
        <h4 id="info">{{year}}/{{month}}/{{date}} <i>by</i> {{author}}</h4>
        <img id="comic" src="{{src}}" title="{{hover}}" alt="Loading, or for some reason this image cannot be found">
    </div>



@@ 36,28 35,31 @@
                src="https://i.creativecommons.org/l/by-nc/4.0/88x31.png" />
        </a>

        <pre class="small">
        <p class="small">
            Comics on fkfd.me are licensed under CC BY-NC 4.0 unless otherwise noted
        </pre>
        </p>
        <hr>
        <p>
            <h4>A few of my favorites: </h4>
            <a href="/55">Python Code With No Documentation</a><br>
            <a href="/67">Irrelevant xkcd</a><br>
            <a href="/69">One-Letter Modifications</a><br>
            <a href="/88">Company Rules</a><br>
            <a href="/90">Stray Cats</a><br>
        </p>
        <hr>
        <p class="small">Art creation of fkfd is based mainly on <a href="https://krita.org">Krita</a>. If you like my
            comics, please consider making a <a href="https://krita.org/en/support-us/donations/">donation</a> for this
            amazing free software project.</p>
        <p class="small">You can obtain the .kra Krita file for most comics,
            by substituting ".png" in the image source file name with ".kra".
        <p class="small">You can obtain the .kra (Krita) and png file for most comics,
            by substituting ".webp" in the image source file name with ".kra" and ".png".
            They are usually hi-res, and layer-accessible for some complex comics,
            in case you would like to make deriviations of fkfd,
            which is always encouraged and appreciated
            as long as (a) the Creative Commons License is not violated,
            and (b) your work contains nothing that makes you an asshole.</p>
        <img id="banner" src="/img/fkfd-integration.png">
            in case you would like to make deriviations of fkfd.</p>
        <p class="small">Comics are occasionally prototyped on a whiteboard. The algorithm runs in O(n↑↑n).
            If few things can go wrong, do it. This site has not yet received any FBI orders or stuff.
            You should trust me because I don't tell lies. I don't do circular logic either.
            If this website someday blows up it is either me having broken Debian or me having broken vim.
        </p>
        <img id="banner" src="/img/fkfd-integration.webp">
    </footer>
</div>
<!--Mastodon Verification-->

D views/edit.handlebars => views/edit.handlebars +0 -30
@@ 1,30 0,0 @@
<header class="box">
    <div id="heading">
        <h1>FKFD</h1>
        <h2>Edit Metadata</h2>
        <p>Hang on, it'll be fixed soon!<sup>&reg;&copy;&trade;</sup></p>
    </div>
</header>
<div id="container" class="box center">
    <div id="upload">
        <h2>Meta.json Editor</h2>
        <h3>Edit Comic (<a href="fkfd.me:8989/#/comics">Link To File Manager</a>)</h3>
        <form name="upload" action="/edit?token={{token}}" method="POST">
            <h3 id="title"><input type="text" id="input-title" name="title" value="{{title}}" placeholder="Comic Title"></h3>
            <span id="info">
                <input type="number" id="month" name="month" value="{{m}}" placeholder="M">/
                <input type="number" id="date"  name="date" value="{{d}}" placeholder="D">/
                <input type="number" id="year"  name="year" value="{{y}}" placeholder="Y"> 
                <br><br><i>by</i>
                <input type="text"   id="author" name="author" value="{{author}}" placeholder="Author">
            </span>
            
            <input class="wide" type="text" id="disp-fn" name="fn" value="${SAFE_TITLE}.png" value="{{fn}}" placeholder="File Name"><br><br>
            <input class="wide" type="text" id="hover" name="hover" value="{{hover}}" placeholder="Hover Text">
            
            <button id="submit" value="Submit">Submit</button>
        </form>
    </div>
</div>

<link rel="stylesheet" href="/css/admin.css">
\ No newline at end of file

M views/layouts/xkcd.handlebars => views/layouts/xkcd.handlebars +7 -2
@@ 1,12 1,17 @@
<!DOCTYPE html>
<html>
    <head>
        <title>fkfd</title>
        <title>fkfd: {{title}}</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="/css/xkcd.css">
        <link rel="icon" href="/img/favicon.png">
        <meta property="og:site_name" content="fkfd" />
        <meta property="og:title" content="{{title}}" />
        <meta property="og:type" content="image/png" />
        <meta property="og:url" content="https://fkfd.me/{{id}}" />
        <meta property="og:image" content="https://fkfd.me{{ogp_image}}" />
    </head>
    <body>
        {{{body}}}
    </body>
</html>
\ No newline at end of file
</html>

D views/search.handlebars => views/search.handlebars +0 -0
D views/token.handlebars => views/token.handlebars +0 -7
@@ 1,7 0,0 @@
<div style="margin: 24px" class="box center">
    <h1>Redirecting to: <a href="/admin?token={{token}}" focus>&gt; Admin</a></h1>
</div>

<script>
    window.location = '/admin?token={{token}}';
</script>
\ No newline at end of file