From eaf2edd6c3dd0a140648bccfdc176997f7e765da Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Tue, 1 Oct 2019 03:45:44 +0000 Subject: [PATCH] Big Optimisations and such --- .circleci/config.yml | 3 + .eslintrc | 3 +- api/article/model.mjs | 11 ++ api/media/model.mjs | 2 +- api/page/model.mjs | 6 + api/serve.mjs | 84 ++++++++++++- app/admin.js | 15 +++ app/admin.scss | 79 +++++++++++++ app/admin/admin.scss | 18 --- app/admin/articles.js | 2 - app/admin/editarticle.js | 5 +- app/admin/editpage.js | 5 +- app/admin/editstaff.js | 2 - app/admin/froala.js | 2 +- app/admin/pages.js | 3 - app/admin/stafflist.js | 2 - app/api/common.js | 1 - app/api/media.js | 1 - app/api/page.js | 4 +- app/app.scss | 120 ++----------------- app/article/article.js | 1 + app/authentication.js | 34 ++++-- app/darkmode.js | 2 +- app/footer/footer.js | 23 ++-- app/footer/footer.scss | 32 +++++ app/froala.scss | 208 +++++++++++++++++++++++++++++++++ app/frontpage/frontpage.js | 50 ++++---- app/frontpage/frontpage.scss | 28 ++++- app/index.js | 70 ++++++++--- app/menu/menu.js | 15 ++- app/menu/menu.scss | 18 +++ app/widgets/admin.scss | 133 +++++++++++++++++++++ app/widgets/common.scss | 159 ++----------------------- app/widgets/dialogue.js | 4 +- app/widgets/fileinfo.js | 3 +- app/widgets/fileupload.js | 1 - app/widgets/newsentry.js | 1 - app/widgets/newsitem.js | 7 +- app/widgets/pages.js | 2 - config/config.default.json | 1 + config/config.default.json.org | 49 ++++++++ package.json | 11 +- public/assets/admin.css | 1 + public/assets/admin.css.map | 1 + public/assets/admin.js | 1 + public/index.html | 14 ++- 46 files changed, 842 insertions(+), 395 deletions(-) create mode 100644 app/admin.js create mode 100644 app/admin.scss create mode 100644 app/froala.scss create mode 100644 app/widgets/admin.scss create mode 100644 config/config.default.json.org create mode 100644 public/assets/admin.css create mode 100644 public/assets/admin.css.map create mode 100644 public/assets/admin.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 03feabf..0ddb056 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,9 @@ jobs: command: apk update && apk upgrade && apk add --no-cache bash git openssh - checkout - setup_remote_docker + - run: + name: Replace version in config + command: sed -i .org "s/circleci_version_number/${CIRCLE_BUILD_NUM}/g" config/config.default.json - run: name: Build docker image command: docker build -t ${di}:build_${CIRCLE_BUILD_NUM} -t ${di}:${CIRCLE_SHA1} -t ${di}:${dtag} . diff --git a/.eslintrc b/.eslintrc index 7dbccca..1d23fea 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,8 @@ }, "globals": { "FroalaEditor": "readonly", - "gapi": "readonly" + "gapi": "readonly", + "m": true }, "extends": "eslint:recommended", "env": { diff --git a/api/article/model.mjs b/api/article/model.mjs index fdbe406..af89e8c 100644 --- a/api/article/model.mjs +++ b/api/article/model.mjs @@ -76,6 +76,17 @@ const Article = bookshelf.createModel({ return result }) }, + + getFrontpageArticles() { + return this.query(qb => { + qb.orderBy('updated_at', 'DESC') + }) + .fetchPage({ + pageSize: 10, + page: 1, + withRelated: ['files', 'media', 'banner'], + }) + }, }) export default Article diff --git a/api/media/model.mjs b/api/media/model.mjs index e029cbe..1a9e944 100644 --- a/api/media/model.mjs +++ b/api/media/model.mjs @@ -44,7 +44,7 @@ const Media = bookshelf.createModel({ }, url() { - return `${Media.baseUrl}${this.get('large_image')}` + return `${Media.baseUrl}${this.get('medium_image')}` }, thumb() { diff --git a/api/page/model.mjs b/api/page/model.mjs index 480c324..30c1adb 100644 --- a/api/page/model.mjs +++ b/api/page/model.mjs @@ -61,6 +61,12 @@ const Page = bookshelf.createModel({ }) .fetch({ require, withRelated, ctx }) }, + getTree() { + return this.query(qb => { + qb.where({ parent_id: null }) + qb.select(['id', 'name', 'path']) + }).fetchAll({ withRelated: ['children'] }) + }, }) export default Page diff --git a/api/serve.mjs b/api/serve.mjs index 5f5ac3e..c651bf4 100644 --- a/api/serve.mjs +++ b/api/serve.mjs @@ -1,5 +1,82 @@ +import { readFileSync } from 'fs' import send from 'koa-send' +import dot from 'dot' import defaults from './defaults.mjs' +import config from './config.mjs' +import Page from './page/model.mjs' +import Article from './article/model.mjs' + +const body = readFileSync('./public/index.html').toString() +const bodyTemplate = dot.template(body) + +async function sendIndex(ctx, path) { + let tree = null + let data = null + let links = null + try { + tree = (await Page.getTree()).toJSON() + tree.forEach(item => ( + item.children = item.children.map(x => ( + { id: x.id, name: x.name, path: x.path } + )) + )) + if (path === '/') { + data = await Article.getFrontpageArticles() + + if (data.pagination.rowCount > 10) { + links = { + current: { title: 'Page 1' }, + next: { page: 2, title: 'Next' }, + last: { page: Math.ceil(data.pagination.rowCount / 10), title: 'Last' }, + } + } else { + links = { + current: { title: 'Page 1' }, + } + } + data = data.toJSON().map(x => ({ + id: x.id, + created_at: x.created_at, + path: x.path, + description: x.description, + name: x.name, + media: x.media && ({ + medium_url: x.media.medium_url, + small_url: x.media.small_url, + }) || null, + banner: x.banner && ({ + large_url: x.banner.large_url, + medium_url: x.banner.medium_url, + small_url: x.banner.small_url, + }) || null, + files: x.files && x.files.map(f => ({ + filename: f.filename, + url: f.url, + magnet: f.magnet, + meta: f.meta.torrent && ({ + torrent: { + files: f.meta.torrent.files.map(tf => ({ + name: tf.name, + size: tf.size, + })), + }, + }) || {}, + })) || [], + })) + } + } catch (e) { + ctx.log.error(e) + } + ctx.body = bodyTemplate({ + v: config.get('CIRCLECI_VERSION'), + tree: JSON.stringify(tree), + data: JSON.stringify(data), + links: JSON.stringify(links), + }) + ctx.set('Content-Length', Buffer.byteLength(ctx.body)) + ctx.set('Cache-Control', 'max-age=0') + ctx.set('Content-Type', 'text/html; charset=utf-8') +} export function serve(docRoot, pathname, options = {}) { options.root = docRoot @@ -22,9 +99,14 @@ export function serve(docRoot, pathname, options = {}) { opts = defaults({ maxage: 2592000 * 1000 }, opts) } + if (filepath === '/index.html') { + return sendIndex(ctx, '/') + } + return send(ctx, filepath, opts).catch((er) => { if (er.code === 'ENOENT' && er.status === 404) { - return send(ctx, '/index.html', options) + return sendIndex(ctx) + // return send(ctx, '/index.html', options) } }) } diff --git a/app/admin.js b/app/admin.js new file mode 100644 index 0000000..5286764 --- /dev/null +++ b/app/admin.js @@ -0,0 +1,15 @@ +const EditPage = require('./admin/editpage') +const AdminPages = require('./admin/pages') +const AdminArticles = require('./admin/articles') +const EditArticle = require('./admin/editarticle') +const AdminStaffList = require('./admin/stafflist') +const EditStaff = require('./admin/editstaff') + +window.addAdminRoutes = [ + ['/admin/pages', AdminPages], + ['/admin/pages/:key', EditPage], + ['/admin/articles', AdminArticles], + ['/admin/articles/:id', EditArticle], + ['/admin/staff', AdminStaffList], + ['/admin/staff/:id', EditStaff], +] diff --git a/app/admin.scss b/app/admin.scss new file mode 100644 index 0000000..003daec --- /dev/null +++ b/app/admin.scss @@ -0,0 +1,79 @@ +@import './_common'; + +.error { + font-size: 0.8em; + color: $secondary-dark-bg; + font-weight: bold; + padding-bottom: 20px; +} + +$bordercolor: $primary-bg; +$headcolor: $primary-light-bg; +$headtext: $primary-light-fg; + +.admin-wrapper table { + width: calc(100% - 20px); + margin: 10px; + border: solid 1px $bordercolor; + border-collapse: collapse; + border-spacing: 0; + font-size: 0.8em; + + thead th { + background-color: $headcolor; + border: solid 1px $bordercolor; + color: $headtext; + padding: 10px; + text-align: left; + } + tbody td { + text-align: left; + border: solid 1px $bordercolor; + color: $table-fg; + padding: 10px; + } + a, + a:visited, + a:hover { + text-decoration: none; + color: $secondary-dark-bg; + font-weight: bold; + } + + button { + color: $secondary-dark-bg; + background: transparent; + border: 1px solid $secondary-dark-bg; + } + + td.right, + th.right { + text-align: right; + } +} + +.floating-container { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #00000099; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +@import 'admin/admin'; +@import 'widgets/admin'; + +.darkmodeon { + .maincontainer .admin-wrapper { + color: $main-fg; + } + + .error { + color: $dark_secondary-dark-bg; + } +} diff --git a/app/admin/admin.scss b/app/admin/admin.scss index bae0155..400f477 100644 --- a/app/admin/admin.scss +++ b/app/admin/admin.scss @@ -38,21 +38,3 @@ @import 'pages'; @import 'articles'; @import 'staff'; - -.darkmodeon { - .admin-wrapper { - background: $dark_primary-bg; - } - - .admin-actions { - background: $dark_primary-bg; - - span { - color: $dark_primary-fg; - } - - a { - color: $dark_secondary-light-bg; - } - } -} diff --git a/app/admin/articles.js b/app/admin/articles.js index f16e7ac..764e9c7 100644 --- a/app/admin/articles.js +++ b/app/admin/articles.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const { getAllArticlesPagination, removeArticle } = require('../api/article') const { fetchPage } = require('../api/pagination') const Dialogue = require('../widgets/dialogue') diff --git a/app/admin/editarticle.js b/app/admin/editarticle.js index 05d708f..2fa6967 100644 --- a/app/admin/editarticle.js +++ b/app/admin/editarticle.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const Authentication = require('../authentication') const FileUpload = require('../widgets/fileupload') const Froala = require('./froala') @@ -120,6 +118,9 @@ const EditArticle = { if (this.error) return this.article.description = vnode.state.froala && vnode.state.froala.html.get() || this.article.description + if (this.article.description) { + this.article.description = this.article.description.replace(/]+data-f-id="pbf"[^>]+>[^>]+>[^>]+>[^>]+>/, '') + } this.loading = true diff --git a/app/admin/editpage.js b/app/admin/editpage.js index 5f9b406..9a1be15 100644 --- a/app/admin/editpage.js +++ b/app/admin/editpage.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const Authentication = require('../authentication') const FileUpload = require('../widgets/fileupload') const Froala = require('./froala') @@ -98,6 +96,9 @@ const EditPage = { if (this.error) return this.page.description = vnode.state.froala ? vnode.state.froala.html.get() : this.page.description + if (this.page.description) { + this.page.description = this.page.description.replace(/]+data-f-id="pbf"[^>]+>[^>]+>[^>]+>[^>]+>/, '') + } this.loading = true diff --git a/app/admin/editstaff.js b/app/admin/editstaff.js index 6091178..32430e1 100644 --- a/app/admin/editstaff.js +++ b/app/admin/editstaff.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const { createStaff, updateStaff, getStaff } = require('../api/staff') const EditStaff = { diff --git a/app/admin/froala.js b/app/admin/froala.js index fc1b214..8fc706b 100644 --- a/app/admin/froala.js +++ b/app/admin/froala.js @@ -33,7 +33,7 @@ const Froala = { element.setAttribute('href', Froala.files[i].url) } else { element = document.createElement('script') - element.setAttribute('type','text/javascript') + element.setAttribute('type', 'text/javascript') element.setAttribute('src', Froala.files[i].url) } element.onload = onload diff --git a/app/admin/pages.js b/app/admin/pages.js index dc56719..3923e48 100644 --- a/app/admin/pages.js +++ b/app/admin/pages.js @@ -1,6 +1,3 @@ -const m = require('mithril') - -const Authentication = require('../authentication') const { getAllPages, removePage } = require('../api/page') const Dialogue = require('../widgets/dialogue') diff --git a/app/admin/stafflist.js b/app/admin/stafflist.js index fc6c436..6347030 100644 --- a/app/admin/stafflist.js +++ b/app/admin/stafflist.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const { getAllStaff, removeStaff } = require('../api/staff') const Dialogue = require('../widgets/dialogue') const Pages = require('../widgets/pages') diff --git a/app/api/common.js b/app/api/common.js index 11a5a84..e02a43e 100644 --- a/app/api/common.js +++ b/app/api/common.js @@ -1,4 +1,3 @@ -const m = require('mithril') const Authentication = require('../authentication') exports.sendRequest = function(options, isPagination) { diff --git a/app/api/media.js b/app/api/media.js index c790f12..2f1a7fd 100644 --- a/app/api/media.js +++ b/app/api/media.js @@ -1,4 +1,3 @@ -const m = require('mithril') const { sendRequest } = require('./common') exports.uploadMedia = function(file) { diff --git a/app/api/page.js b/app/api/page.js index 097857a..94a62e1 100644 --- a/app/api/page.js +++ b/app/api/page.js @@ -1,6 +1,6 @@ const { sendRequest } = require('./common') -const Tree = [] +const Tree = window.__nfptree || [] exports.Tree = Tree @@ -25,7 +25,7 @@ exports.createPage = function(body) { }) } -exports.getTree = function(body) { +exports.getTree = function() { return sendRequest({ method: 'GET', url: '/api/pages?tree=true&includes=children&fields=id,name,path,children(id,name,path)', diff --git a/app/app.scss b/app/app.scss index b5dcc3d..ebf42b9 100644 --- a/app/app.scss +++ b/app/app.scss @@ -167,88 +167,11 @@ form { } } -.fr-view { - .clearfix::after{clear:both;display:block;content:"";height:0}.hide-by-clipping{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}img.fr-rounded,.fr-img-caption.fr-rounded img{border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box}img.fr-bordered,.fr-img-caption.fr-bordered img{border:solid 5px #CCC}img.fr-bordered{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fr-img-caption.fr-bordered img{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.fr-view{word-wrap:break-word}.fr-view span[style~="color:"] a{color:inherit}.fr-view strong{font-weight:700}.fr-view table{border:none;border-collapse:collapse;empty-cells:show;max-width:100%}.fr-view table td{min-width:5px}.fr-view table.fr-dashed-borders td,.fr-view table.fr-dashed-borders th{border-style:dashed}.fr-view table.fr-alternate-rows tbody tr:nth-child(2n){background:whitesmoke}.fr-view table td,.fr-view table th{border:1px solid #DDD}.fr-view table td:empty,.fr-view table th:empty{height:20px}.fr-view table td.fr-highlighted,.fr-view table th.fr-highlighted{border:1px double red}.fr-view table td.fr-thick,.fr-view table th.fr-thick{border-width:2px}.fr-view table th{background:#ececec}.fr-view hr{clear:both;user-select:none;-o-user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-ms-user-select:none;page-break-after:always}.fr-view .fr-file{position:relative}.fr-view .fr-file::after{position:relative;content:"\1F4CE";font-weight:normal}.fr-view pre{white-space:pre-wrap;word-wrap:break-word;overflow:visible}.fr-view[dir="rtl"] blockquote{border-left:none;border-right:solid 2px #5E35B1;margin-right:0;padding-right:5px;padding-left:0}.fr-view[dir="rtl"] blockquote blockquote{border-color:#00BCD4}.fr-view[dir="rtl"] blockquote blockquote blockquote{border-color:#43A047}.fr-view blockquote{border-left:solid 2px #5E35B1;margin-left:0;padding-left:5px;color:#5E35B1}.fr-view blockquote blockquote{border-color:#00BCD4;color:#00BCD4}.fr-view blockquote blockquote blockquote{border-color:#43A047;color:#43A047}.fr-view span.fr-emoticon{font-weight:normal;font-family:"Apple Color Emoji","Segoe UI Emoji","NotoColorEmoji","Segoe UI Symbol","Android Emoji","EmojiSymbols";display:inline;line-height:0}.fr-view span.fr-emoticon.fr-emoticon-img{background-repeat:no-repeat !important;font-size:inherit;height:1em;width:1em;min-height:20px;min-width:20px;display:inline-block;margin:-.1em .1em .1em;line-height:1;vertical-align:middle}.fr-view .fr-text-gray{color:#AAA !important}.fr-view .fr-text-bordered{border-top:solid 1px #222;border-bottom:solid 1px #222;padding:10px 0}.fr-view .fr-text-spaced{letter-spacing:1px}.fr-view .fr-text-uppercase{text-transform:uppercase}.fr-view .fr-class-highlighted{background-color:#ffff00}.fr-view .fr-class-code{border-color:#cccccc;border-radius:2px;-moz-border-radius:2px;-webkit-border-radius:2px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;background:#f5f5f5;padding:10px;font-family:"Courier New", Courier, monospace}.fr-view .fr-class-transparency{opacity:0.5}.fr-view img{position:relative;max-width:100%}.fr-view img.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}.fr-view img.fr-dib.fr-fil{margin-left:0;text-align:left}.fr-view img.fr-dib.fr-fir{margin-right:0;text-align:right}.fr-view img.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}.fr-view img.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}.fr-view img.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)}.fr-view span.fr-img-caption{position:relative;max-width:100%}.fr-view span.fr-img-caption.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}.fr-view span.fr-img-caption.fr-dib.fr-fil{margin-left:0;text-align:left}.fr-view span.fr-img-caption.fr-dib.fr-fir{margin-right:0;text-align:right}.fr-view span.fr-img-caption.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}.fr-view span.fr-img-caption.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}.fr-view span.fr-img-caption.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)}.fr-view .fr-video{text-align:center;position:relative}.fr-view .fr-video.fr-rv{padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.fr-view .fr-video.fr-rv>iframe,.fr-view .fr-video.fr-rv object,.fr-view .fr-video.fr-rv embed{position:absolute !important;top:0;left:0;width:100%;height:100%}.fr-view .fr-video>*{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;max-width:100%;border:none}.fr-view .fr-video.fr-dvb{display:block;clear:both}.fr-view .fr-video.fr-dvb.fr-fvl{text-align:left}.fr-view .fr-video.fr-dvb.fr-fvr{text-align:right}.fr-view .fr-video.fr-dvi{display:inline-block}.fr-view .fr-video.fr-dvi.fr-fvl{float:left}.fr-view .fr-video.fr-dvi.fr-fvr{float:right}.fr-view a.fr-strong{font-weight:700}.fr-view a.fr-green{color:green}.fr-view .fr-img-caption{text-align:center}.fr-view .fr-img-caption .fr-img-wrap{padding:0;display:inline-block;margin:auto;text-align:center;width:100%}.fr-view .fr-img-caption .fr-img-wrap img{display:block;margin:auto;width:100%}.fr-view .fr-img-caption .fr-img-wrap>span{margin:auto;display:block;padding:5px 5px 10px;font-size:14px;font-weight:initial;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-opacity:0.9;-moz-opacity:0.9;opacity:0.9;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";width:100%;text-align:center}.fr-view button.fr-rounded,.fr-view input.fr-rounded,.fr-view textarea.fr-rounded{border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box}.fr-view button.fr-large,.fr-view input.fr-large,.fr-view textarea.fr-large{font-size:24px}a.fr-view.fr-strong{font-weight:700}a.fr-view.fr-green{color:green}img.fr-view{position:relative;max-width:100%}img.fr-view.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}img.fr-view.fr-dib.fr-fil{margin-left:0;text-align:left}img.fr-view.fr-dib.fr-fir{margin-right:0;text-align:right}img.fr-view.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}img.fr-view.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}img.fr-view.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)}span.fr-img-caption.fr-view{position:relative;max-width:100%}span.fr-img-caption.fr-view.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}span.fr-img-caption.fr-view.fr-dib.fr-fil{margin-left:0;text-align:left}span.fr-img-caption.fr-view.fr-dib.fr-fir{margin-right:0;text-align:right}span.fr-img-caption.fr-view.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}span.fr-img-caption.fr-view.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}span.fr-img-caption.fr-view.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)} - h1, h2, h3, h4, h5, h6, p, dl, ol, ul { - margin: 0 0 1em !important; - } - - a { color: $secondary-dark-bg; } - dt { font-weight: bold; } - ol { list-style-type: decimal; padding-left: 40px; } - ul { list-style-type: disc; padding-left: 40px; } - h1 { font-size: 1.8em; font-weight: bold; } - h2 { font-size: 1.6em; font-weight: bold; } - h3 { font-size: 1.4em; font-weight: bold; } - h4 { font-size: 1.2em; font-weight: bold; } - h5 { font-size: 1.0em; font-weight: bold; } - h6 { font-size: 0.8em; font-weight: bold; } - hr { width: 100%; } - -} - -$bordercolor: $primary-bg; -$headcolor: $primary-light-bg; -$headtext: $primary-light-fg; - -table { - width: calc(100% - 20px); - margin: 10px; - border: solid 1px $bordercolor; - border-collapse: collapse; - border-spacing: 0; - font-size: 0.8em; - - thead th { - background-color: $headcolor; - border: solid 1px $bordercolor; - color: $headtext; - padding: 10px; - text-align: left; - } - tbody td { - text-align: left; - border: solid 1px $bordercolor; - color: $table-fg; - padding: 10px; - } - a, - a:visited, - a:hover { - text-decoration: none; - color: $secondary-dark-bg; - font-weight: bold; - } - - button { - color: $secondary-dark-bg; - background: transparent; - border: 1px solid $secondary-dark-bg; - } - - td.right, - th.right { - text-align: right; - } -} - -.floating-container { - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; - background: #00000099; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} +@import 'froala'; @import 'menu/menu'; @import 'footer/footer'; @import 'login/login'; -@import 'admin/admin'; @import 'widgets/common'; @import 'pages/page'; @import 'article/article'; @@ -264,10 +187,17 @@ table { color: $dark_secondary-dark-bg; } - article { + .fr-view blockquote { + border-left-color: $dark_main-fg; + color: $dark_main-fg; + } + + article.article, + article.login, + article.page { header { h1 { - color: $dark_title-fg; + // color: $dark_title-fg; } span { @@ -282,7 +212,7 @@ table { } } - form { + .login form { input[type=text], input[type=password], select, @@ -312,32 +242,4 @@ table { .fr-view { a { color: $dark_secondary-dark-bg; } } - - $dark_bordercolor: $dark_primary-bg; - $dark_headcolor: $dark_primary-light-bg; - $dark_headtext: $dark_primary-light-fg; - - table { - border: solid 1px $dark_bordercolor; - - thead th { - background-color: $dark_headcolor; - border: solid 1px $dark_bordercolor; - color: $dark_headtext; - } - tbody td { - border: solid 1px $dark_bordercolor; - color: $dark_table-fg; - } - a, - a:visited, - a:hover { - color: $dark_secondary-dark-bg; - } - - button { - color: $dark_secondary-dark-bg; - border: 1px solid $dark_secondary-dark-bg; - } - } } diff --git a/app/article/article.js b/app/article/article.js index c376123..7df4590 100644 --- a/app/article/article.js +++ b/app/article/article.js @@ -54,6 +54,7 @@ const Article = { m('.fr-view', [ this.article.media ? m('a.cover', { + rel: 'noopener', href: this.article.media.url, }, m('img', { src: this.article.media.medium_url, alt: 'Cover image for ' + this.article.name })) : null, diff --git a/app/authentication.js b/app/authentication.js index 12fa25c..992f8be 100644 --- a/app/authentication.js +++ b/app/authentication.js @@ -1,30 +1,35 @@ -const m = require('mithril') - const storageName = 'logintoken' -const loadingListeners = [] - -window.googleLoaded = function() { - Authentication.loadedGoogle = true - while (Authentication.loadingListeners.length) { - Authentication.loadingListeners.pop()() - } -} const Authentication = { currentUser: null, + isAdmin: false, loadedGoogle: false, loadingGoogle: false, loadingListeners: [], + authListeners: [], updateToken: function(token) { if (!token) return Authentication.clearToken() localStorage.setItem(storageName, token) Authentication.currentUser = JSON.parse(atob(token.split('.')[1])) + + if (Authentication.authListeners.length) { + Authentication.authListeners.forEach(function(x) { x(Authentication.currentUser) }) + } }, clearToken: function() { Authentication.currentUser = null localStorage.removeItem(storageName) + Authentication.isAdmin = false + }, + + addEvent: function(event) { + Authentication.authListeners.push(event) + }, + + setAdmin: function(item) { + Authentication.isAdmin = item }, createGoogleScript: function() { @@ -50,6 +55,15 @@ const Authentication = { }, } +if (!window.googleLoaded) { + window.googleLoaded = function() { + Authentication.loadedGoogle = true + while (Authentication.loadingListeners.length) { + Authentication.loadingListeners.pop()() + } + } +} + Authentication.updateToken(localStorage.getItem(storageName)) module.exports = Authentication diff --git a/app/darkmode.js b/app/darkmode.js index a575806..a230631 100644 --- a/app/darkmode.js +++ b/app/darkmode.js @@ -10,7 +10,7 @@ const Darkmode = { Darkmode.darkIsOn = true } else { localStorage.removeItem(storageName) - document.body.className = '' + document.body.className = 'daymode' Darkmode.darkIsOn = false } }, diff --git a/app/footer/footer.js b/app/footer/footer.js index 8845585..3a469c2 100644 --- a/app/footer/footer.js +++ b/app/footer/footer.js @@ -1,7 +1,6 @@ const m = require('mithril') const { Tree } = require('../api/page') const Authentication = require('../authentication') -const Darkmode = require('../darkmode') const Footer = { oninit: function(vnode) { @@ -9,11 +8,6 @@ const Footer = { }, view: function() { - var pixelRatio = window.devicePixelRatio || 1 - var darkPrefix = '' - if (Darkmode.darkIsOn) { - darkPrefix = 'dark_' - } return [ m('div.sitemap', [ m('div', 'Sitemap'), @@ -31,16 +25,15 @@ const Footer = { !Authentication.currentUser ? m(m.route.Link, { class: 'root', href: '/login' }, 'Login') : null, - m('div.meta', ['©' - + this.year - + ' NFP Encodes - nfp@nfp.moe - ', - m('a', { href: 'https://www.iubenda.com/privacy-policy/31076050', target: '_blank' }, 'Privacy Policy'), - ' (Fuck EU)', - ]) + m('div.meta', [ + '©' + + this.year + + ' NFP Encodes - nfp@nfp.moe - ', + m('a', { rel: 'noopener', href: 'https://www.iubenda.com/privacy-policy/31076050', target: '_blank' }, 'Privacy Policy'), + ' (Fuck EU)', + ]), ]), - m('div.footer-logo', { style: { - 'background-image': pixelRatio > 1 ? 'url("/assets/img/' + darkPrefix + 'tsun.jpg")' : 'url("/assets/img/' + darkPrefix + 'tsun_small.jpg")' - } }), + m('div.footer-logo'), ] }, } diff --git a/app/footer/footer.scss b/app/footer/footer.scss index 41d2679..2f92f4d 100644 --- a/app/footer/footer.scss +++ b/app/footer/footer.scss @@ -73,3 +73,35 @@ footer { } } } + +.daymode footer .footer-logo { + background-image: url("/assets/img/tsun_small.jpg"); +} + +.darkmodeon footer .footer-logo { + background-image: url("/assets/img/dark_tsun_small.jpg"); +} + +@media +only screen and (-webkit-min-device-pixel-ratio: 2), +only screen and ( min--moz-device-pixel-ratio: 2), +only screen and ( -o-min-device-pixel-ratio: 2/1), +only screen and ( min-device-pixel-ratio: 2), +only screen and ( min-resolution: 192dpi), +only screen and ( min-resolution: 2dppx) { + .daymode .footer-logo { + background-image: url("/assets/img/tsun.jpg"); + } + + .darkmodeon .footer-logo { + background-image: url("/assets/img/dark_tsun.jpg"); + } +} + +@media (pointer:coarse) { + footer .sitemap a.root, + footer .sitemap a.child { + // padding: 10px 10px; + // display: inline-block; + } +} diff --git a/app/froala.scss b/app/froala.scss new file mode 100644 index 0000000..20920c8 --- /dev/null +++ b/app/froala.scss @@ -0,0 +1,208 @@ +.fr-view { + word-wrap: break-word; + + .clearfix::after { + clear: both; + display: block; + content: ""; + height: 0 + } + h1, h2, h3, h4, h5, h6, p, dl, ol, ul { + margin: 0 0 1em !important; + } + + blockquote { + border-left: solid 2px $main-fg; + margin-left: 0; + padding-left: 5px; + color: $main-fg; + } + + .hide-by-clipping { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0 + } + + img.fr-rounded, + .fr-img-caption.fr-rounded img { + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -moz-background-clip: padding; + -webkit-background-clip: padding-box; + background-clip: padding-box + } + + img.fr-bordered, + .fr-img-caption.fr-bordered img { + border: solid 5px #CCC + } + + img.fr-bordered { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box + } + + .fr-img-caption.fr-bordered img { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box + } + + span.fr-emoticon { + font-weight: normal; + font-family: "Apple Color Emoji", "Segoe UI Emoji", "NotoColorEmoji", "Segoe UI Symbol", "Android Emoji", "EmojiSymbols"; + display: inline; + line-height: 0 + } + + img { + position: relative; + max-width: 100% + } + + img.fr-dib { + margin: 5px auto; + display: block; + float: none; + vertical-align: top + } + + img.fr-dib.fr-fil { + margin-left: 0; + text-align: left + } + + img.fr-dib.fr-fir { + margin-right: 0; + text-align: right + } + + img.fr-dii { + display: inline-block; + float: none; + vertical-align: bottom; + margin-left: 5px; + margin-right: 5px; + max-width: calc(100% - (2 * 5px)) + } + + img.fr-dii.fr-fil { + float: left; + margin: 5px 5px 5px 0; + max-width: calc(100% - 5px) + } + + img.fr-dii.fr-fir { + float: right; + margin: 5px 0 5px 5px; + max-width: calc(100% - 5px) + } + + span.fr-img-caption { + position: relative; + max-width: 100% + } + + span.fr-img-caption.fr-dib { + margin: 5px auto; + display: block; + float: none; + vertical-align: top + } + + span.fr-img-caption.fr-dib.fr-fil { + margin-left: 0; + text-align: left + } + + span.fr-img-caption.fr-dib.fr-fir { + margin-right: 0; + text-align: right + } + + span.fr-img-caption.fr-dii { + display: inline-block; + float: none; + vertical-align: bottom; + margin-left: 5px; + margin-right: 5px; + max-width: calc(100% - (2 * 5px)) + } + + span.fr-img-caption.fr-dii.fr-fil { + float: left; + margin: 5px 5px 5px 0; + max-width: calc(100% - 5px) + } + + span.fr-img-caption.fr-dii.fr-fir { + float: right; + margin: 5px 0 5px 5px; + max-width: calc(100% - 5px) + } + + a.fr-strong { + font-weight: 700 + } + + a.fr-green { + color: green + } + + .fr-img-caption { + text-align: center + } + + .fr-img-caption .fr-img-wrap { + padding: 0; + display: inline-block; + margin: auto; + text-align: center; + width: 100% + } + + .fr-img-caption .fr-img-wrap img { + display: block; + margin: auto; + width: 100% + } + + .fr-img-caption .fr-img-wrap>span { + margin: auto; + display: block; + padding: 5px 5px 10px; + font-size: 14px; + font-weight: initial; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-opacity: 0.9; + -moz-opacity: 0.9; + opacity: 0.9; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + width: 100%; + text-align: center + } + + a { color: $secondary-dark-bg; } + dt { font-weight: bold; } + ol { list-style-type: decimal; padding-left: 40px; } + ul { list-style-type: disc; padding-left: 40px; } + h1 { font-size: 1.8em; font-weight: bold; } + h2 { font-size: 1.6em; font-weight: bold; } + h3 { font-size: 1.4em; font-weight: bold; } + h4 { font-size: 1.2em; font-weight: bold; } + h5 { font-size: 1.0em; font-weight: bold; } + h6 { font-size: 0.8em; font-weight: bold; } + hr { width: 100%; } + strong { font-weight: 700 } + +} diff --git a/app/frontpage/frontpage.js b/app/frontpage/frontpage.js index 2f7b219..b1a26ce 100644 --- a/app/frontpage/frontpage.js +++ b/app/frontpage/frontpage.js @@ -5,15 +5,25 @@ const { getAllArticlesPagination } = require('../api/article') const { fetchPage } = require('../api/pagination') const Pages = require('../widgets/pages') const Newsitem = require('../widgets/newsitem') -const Darkmode = require('../darkmode') -module.exports = { +const Frontpage = { oninit: function(vnode) { this.error = '' this.loading = false this.featured = null this.links = null - this.fetchArticles(vnode) + + if (window.__nfpdata + && window.__nfplinks) { + this.links = window.__nfplinks + this.articles = window.__nfpdata + this.lastpage = '1' + window.__nfpdata = null + window.__nfplinks = null + Frontpage.processFeatured(vnode, this.articles) + } else { + this.fetchArticles(vnode) + } }, onupdate: function(vnode) { @@ -30,19 +40,14 @@ module.exports = { this.lastpage = m.route.param('page') || '1' return fetchPage(getAllArticlesPagination({ - per_page: 10, + per_page: 2, page: this.lastpage, includes: ['parent', 'files', 'media', 'banner'], })) .then(function(result) { vnode.state.articles = result.data vnode.state.links = result.links - - for (var i = result.data.length - 1; i >= 0; i--) { - if (result.data[i].banner) { - vnode.state.featured = result.data[i] - } - } + Frontpage.processFeatured(vnode, result.data) }) .catch(function(err) { vnode.state.error = err.message @@ -53,19 +58,18 @@ module.exports = { }) }, + processFeatured: function(vnode, data) { + for (var i = data.length - 1; i >= 0; i--) { + if (data[i].banner) { + vnode.state.featured = data[i] + } + } + }, + view: function(vnode) { var deviceWidth = window.innerWidth var bannerPath = '' - var asuna_side = '' - - if (deviceWidth > 800) { - if (Darkmode.darkIsOn) { - asuna_side = '/assets/img/dark_asuna_frontpage.jpg' - } else { - asuna_side = '/assets/img/asuna_frontpage.jpg' - } - } if (this.featured && this.featured.banner) { var pixelRatio = window.devicePixelRatio || 1 @@ -75,12 +79,12 @@ module.exports = { || (deviceWidth < 600 && pixelRatio > 1)) { bannerPath = this.featured.banner.medium_url } else { - bannerPath = this.featured.banner.url + bannerPath = this.featured.banner.large_url } } return [ - (this.featured && this.featured.banner + (bannerPath ? m('a.frontpage-banner', { href: '/article/' + this.featured.path, style: { 'background-image': 'url("' + bannerPath + '")' }, @@ -103,7 +107,7 @@ module.exports = { ] }), ]), - asuna_side ? m('img', { src: asuna_side, alt: 'Asuna standing tall welcomes you' }) : null, + m('div.asunaside'), ]), m('.frontpage-news', [ (this.loading @@ -121,3 +125,5 @@ module.exports = { ] }, } + +module.exports = Frontpage diff --git a/app/frontpage/frontpage.scss b/app/frontpage/frontpage.scss index be63ba2..11e3251 100644 --- a/app/frontpage/frontpage.scss +++ b/app/frontpage/frontpage.scss @@ -38,10 +38,6 @@ frontpage { display: flex; flex-direction: column; - img { - align-self: center; - } - .categories { padding: 10px 10px 20px; margin-bottom: 20px; @@ -89,6 +85,10 @@ frontpage { newsitem { margin-bottom: 30px; } + + .asunaside { + display: none; + } } @media screen and (max-width: 1000px){ @@ -113,6 +113,26 @@ frontpage { } } +@media screen and (min-width: 800px){ + frontpage .asunaside { + display: block; + width: 200px; + height: 480px; + background-size: contain; + background-repeat: no-repeat; + background-position: top left; + align-self: center; + } + + .daymode frontpage .asunaside { + background-image: url("/assets/img/asuna_frontpage.jpg"); + } + + .darkmodeon frontpage .asunaside { + background-image: url("/assets/img/dark_asuna_frontpage.jpg"); + } +} + @media screen and (max-width: 480px){ .frontpage-banner { width: 100%; diff --git a/app/index.js b/app/index.js index bb39775..604c0c8 100644 --- a/app/index.js +++ b/app/index.js @@ -1,4 +1,5 @@ const m = require('mithril') +window.m = m m.route.prefix = '' @@ -7,31 +8,72 @@ const Footer = require('./footer/footer') const Frontpage = require('./frontpage/frontpage') const Login = require('./login/login') const Logout = require('./login/logout') -const EditPage = require('./admin/editpage') const Page = require('./pages/page') const Article = require('./article/article') -const AdminPages = require('./admin/pages') -const AdminArticles = require('./admin/articles') -const EditArticle = require('./admin/editarticle') -const AdminStaffList = require('./admin/stafflist') -const EditStaff = require('./admin/editstaff') +const Authentication = require('./authentication') const menuRoot = document.getElementById('nav') const mainRoot = document.getElementById('main') const footerRoot = document.getElementById('footer') -m.route(mainRoot, '/', { +const allRoutes = { '/': Frontpage, '/login': Login, '/logout': Logout, '/page/:id': Page, '/article/:id': Article, - '/admin/pages': AdminPages, - '/admin/pages/:key': EditPage, - '/admin/articles': AdminArticles, - '/admin/articles/:id': EditArticle, - '/admin/staff': AdminStaffList, - '/admin/staff/:id': EditStaff, -}) +} + +m.route(mainRoot, '/', allRoutes) m.mount(menuRoot, Menu) m.mount(footerRoot, Footer) + +let loadingAdmin = false +let loadedAdmin = false +let loaded = 0 + +const onLoaded = function() { + loaded++ + if (loaded < 2) return + + if (window.addAdminRoutes) { + window.addAdminRoutes.forEach(function (item) { + allRoutes[item[0]] = item[1] + }) + m.route(mainRoot, '/', allRoutes) + } + + Authentication.setAdmin(Authentication.currentUser && Authentication.currentUser.level >= 10) + loadedAdmin = true + m.redraw() +} + +const loadAdmin = function(user) { + if (loadingAdmin) { + if (loadedAdmin) { + Authentication.setAdmin(user && user.level >= 10) + } + return + } + if (!user || user.level < 10) return + + loadingAdmin = true + + let element = document.createElement('link') + element.setAttribute('rel', 'stylesheet') + element.setAttribute('type', 'text/css') + element.setAttribute('href', '/assets/admin.css') + element.onload = onLoaded + document.getElementsByTagName('head')[0].appendChild(element) + + element = document.createElement('script') + element.setAttribute('type', 'text/javascript') + element.setAttribute('src', '/assets/admin.js') + element.onload = onLoaded + document.body.appendChild(element) +} + +Authentication.addEvent(loadAdmin) +if (Authentication.currentUser) { + loadAdmin(Authentication.currentUser) +} diff --git a/app/menu/menu.js b/app/menu/menu.js index 2a17057..a4b5fc9 100644 --- a/app/menu/menu.js +++ b/app/menu/menu.js @@ -18,11 +18,13 @@ const Menu = { oninit: function(vnode) { Menu.onbeforeupdate() + if (Tree.length) return + Menu.loading = true getTree() .then(function(results) { - Tree.splice(0, Tree.Length) + Tree.splice(0, Tree.length) Tree.push.apply(Tree, results) }) .catch(function(err) { @@ -35,13 +37,10 @@ const Menu = { }, view: function() { - var pixelRatio = window.devicePixelRatio || 1 return [ m('div.top', [ m(m.route.Link, - { href: '/', class: 'logo', style: { - 'background-image': pixelRatio > 1 ? 'url("/assets/img/logo.jpg")' : 'url("/assets/img/logo_small.jpg")' - } }, + { href: '/', class: 'logo' }, m('h2', 'NFP Moe') ), m('aside', Authentication.currentUser ? [ @@ -51,16 +50,16 @@ const Menu = { (Darkmode.darkIsOn ? m('button.dark', { onclick: Darkmode.setDarkMode.bind(Darkmode, false) }, 'Day mode') : m('button.dark', { onclick: Darkmode.setDarkMode.bind(Darkmode, true) }, 'Night mode') - ) + ), ]), - (Authentication.currentUser.level >= 10 + (Authentication.isAdmin ? m('div.adminlinks', [ m(m.route.Link, { href: '/admin/articles/add' }, 'Create article'), m(m.route.Link, { href: '/admin/articles' }, 'Articles'), m(m.route.Link, { href: '/admin/pages' }, 'Pages'), m(m.route.Link, { hidden: Authentication.currentUser.level < 100, href: '/admin/staff' }, 'Staff'), ]) - : null + : (Authentication.currentUser.level > 10 ? m('div.loading-spinner') : null) ), ] : (Darkmode.darkIsOn ? m('button.dark', { onclick: Darkmode.setDarkMode.bind(Darkmode, false) }, 'Day mode') diff --git a/app/menu/menu.scss b/app/menu/menu.scss index 9aded18..6c5b6a9 100644 --- a/app/menu/menu.scss +++ b/app/menu/menu.scss @@ -26,6 +26,7 @@ display: flex; color: $primary-dark-fg; text-decoration: none; + background-image: url("/assets/img/logo_small.jpg"); } h2 { @@ -74,6 +75,11 @@ min-width: 100px; } } + + .loading-spinner { + position: relative; + width: 200px; + } } } @@ -121,6 +127,18 @@ } } +@media +only screen and (-webkit-min-device-pixel-ratio: 2), +only screen and ( min--moz-device-pixel-ratio: 2), +only screen and ( -o-min-device-pixel-ratio: 2/1), +only screen and ( min-device-pixel-ratio: 2), +only screen and ( min-resolution: 192dpi), +only screen and ( min-resolution: 2dppx) { + #nav .top a.logo { + background-image: url("/assets/img/logo.jpg"); + } +} + .darkmodeon { #nav { .top { diff --git a/app/widgets/admin.scss b/app/widgets/admin.scss new file mode 100644 index 0000000..9edc89a --- /dev/null +++ b/app/widgets/admin.scss @@ -0,0 +1,133 @@ + +fileupload { + position: relative; + display: flex; + align-items: stretch; + flex-direction: column; + justify-content: stretch; + + .showicon, + .showbordericon, + .display { + flex-grow: 2; + } + + .showbordericon { + border: 3px solid $title-fg; + border-style: dashed; + background-image: url('./img/upload.svg'); + background-position: center; + background-repeat: no-repeat; + background-size: 50px; + } + + .showicon { + position: absolute; + top: 5px; + right: 5px; + width: 50px; + height: 50px; + background-image: url('./img/upload.svg'); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + } + + img { + max-width: 600px; + width: 100%; + align-self: center; + min-height: 100px; + } + + .display { + background-size: cover; + background-repeat: no-repeat; + background-position: center; + } + + .loading-spinner { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #33333388; + width: 100%; + } + + input { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + opacity: 0.01; + width: 100%; + cursor: pointer; + text-indent: -9999px; + z-index: 2; + } + + .remove { + border: none; + position: absolute; + top: 5px; + right: 60px; + width: 50px; + height: 50px; + background-image: url('./img/delete.svg'); + background-position: center; + background-repeat: no-repeat; + background-color: transparent; + background-size: contain; + z-index: 3; + outline: none; + cursor: pointer; + } +} + +dialogue { + background: white; + display: flex; + flex-direction: column; + text-align: center; + width: calc(100% - 40px); + max-width: 500px; + color: $main-fg; + + h2 { + background: $secondary-dark-bg; + color: $secondary-dark-fg; + font-size: 1.5em; + padding: 10px; + } + + p { + padding: 10px; + } + + .buttons { + display: flex; + justify-content: space-around; + padding: 10px; + } + + button { + border: 1px solid $secondary-dark-bg; + background: transparent; + color: $secondary-dark-bg; + padding: 5px 15px; + min-width: 150px; + } + + button.alert { + border-color: red; + color: red; + } + + button.cancel { + border-color: #999; + color: #999; + } +} diff --git a/app/widgets/common.scss b/app/widgets/common.scss index c94c620..9c1ce5f 100644 --- a/app/widgets/common.scss +++ b/app/widgets/common.scss @@ -1,137 +1,4 @@ - -fileupload { - position: relative; - display: flex; - align-items: stretch; - flex-direction: column; - justify-content: stretch; - - .showicon, - .showbordericon, - .display { - flex-grow: 2; - } - - .showbordericon { - border: 3px solid $title-fg; - border-style: dashed; - background-image: url('./img/upload.svg'); - background-position: center; - background-repeat: no-repeat; - background-size: 50px; - } - - .showicon { - position: absolute; - top: 5px; - right: 5px; - width: 50px; - height: 50px; - background-image: url('./img/upload.svg'); - background-position: center; - background-repeat: no-repeat; - background-size: contain; - } - - img { - max-width: 600px; - width: 100%; - align-self: center; - min-height: 100px; - } - - .display { - background-size: cover; - background-repeat: no-repeat; - background-position: center; - } - - .loading-spinner { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: #33333388; - width: 100%; - } - - input { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - opacity: 0.01; - width: 100%; - cursor: pointer; - text-indent: -9999px; - z-index: 2; - } - - .remove { - border: none; - position: absolute; - top: 5px; - right: 60px; - width: 50px; - height: 50px; - background-image: url('./img/delete.svg'); - background-position: center; - background-repeat: no-repeat; - background-color: transparent; - background-size: contain; - z-index: 3; - outline: none; - cursor: pointer; - } -} - -dialogue { - background: white; - display: flex; - flex-direction: column; - text-align: center; - width: calc(100% - 40px); - max-width: 500px; - - h2 { - background: $secondary-dark-bg; - color: $secondary-dark-fg; - font-size: 1.5em; - padding: 10px; - } - - p { - padding: 10px; - } - - .buttons { - display: flex; - justify-content: space-around; - padding: 10px; - } - - button { - border: 1px solid $secondary-dark-bg; - background: transparent; - color: $secondary-dark-bg; - padding: 5px 15px; - min-width: 150px; - } - - button.alert { - border-color: red; - color: red; - } - - button.cancel { - border-color: #999; - color: #999; - } -} - newsentry { display: flex; color: $meta-fg; @@ -275,6 +142,10 @@ newsitem { flex: 2 1 auto; padding: 0 5px 5px; + &.extrapadding { + padding: 0 15px 5px; + } + h3 { margin-bottom: 0 !important; font-size: 1.3em; @@ -342,28 +213,14 @@ pages { .newsitemcontent { flex-direction: column; } + + .entrycontent.extrapadding { + padding: 0 5px 5px; + } } } .darkmodeon { - fileupload { - .showbordericon { - border: 3px solid $dark_title-fg; - } - } - - dialogue { - h2 { - background: $dark_secondary-dark-bg; - color: $dark_secondary-dark-fg; - } - - button { - border: 1px solid $dark_secondary-dark-bg; - color: $dark_secondary-dark-bg; - } - } - newsentry { color: $dark_meta-fg; diff --git a/app/widgets/dialogue.js b/app/widgets/dialogue.js index fcbba92..e2ac9a1 100644 --- a/app/widgets/dialogue.js +++ b/app/widgets/dialogue.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const Dialogue = { view: function(vnode) { return m('div.floating-container', { @@ -10,7 +8,7 @@ const Dialogue = { m('div.buttons', [ m('button', { class: vnode.attrs.yesclass || '', onclick: vnode.attrs.onyes }, vnode.attrs.yes), m('button', { class: vnode.attrs.noclass || '', onclick: vnode.attrs.onno }, vnode.attrs.no), - ]) + ]), ]) ) }, diff --git a/app/widgets/fileinfo.js b/app/widgets/fileinfo.js index 546a47b..888417f 100644 --- a/app/widgets/fileinfo.js +++ b/app/widgets/fileinfo.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const Fileinfo = { getPrefix(vnode) { if (!vnode.attrs.file.filename.endsWith('.torrent')) { @@ -49,6 +47,7 @@ const Fileinfo = { m('span.prefix', this.getPrefix(vnode) + ':'), m('a', { target: '_blank', + rel: 'noopener', href: vnode.attrs.file.url, }, this.getDownloadName(vnode)), vnode.attrs.file.magnet diff --git a/app/widgets/fileupload.js b/app/widgets/fileupload.js index 8ee8ec6..b1087b6 100644 --- a/app/widgets/fileupload.js +++ b/app/widgets/fileupload.js @@ -1,4 +1,3 @@ -const m = require('mithril') const { uploadMedia } = require('../api/media') const FileUpload = { diff --git a/app/widgets/newsentry.js b/app/widgets/newsentry.js index fa84097..848db57 100644 --- a/app/widgets/newsentry.js +++ b/app/widgets/newsentry.js @@ -1,4 +1,3 @@ -const m = require('mithril') const Fileinfo = require('./fileinfo') const Newsentry = { diff --git a/app/widgets/newsitem.js b/app/widgets/newsitem.js index 9b82a17..66c4b74 100644 --- a/app/widgets/newsitem.js +++ b/app/widgets/newsitem.js @@ -1,4 +1,3 @@ -const m = require('mithril') const Fileinfo = require('./fileinfo') const Newsitem = { @@ -14,8 +13,10 @@ const Newsitem = { ? m('a.cover', { href: '/article/' + vnode.attrs.path, }, m('img', { alt: 'Image for news item ' + vnode.attrs.name, src: pixelRatio > 1 ? vnode.attrs.media.medium_url : vnode.attrs.media.small_url })) - : m('a.cover.nobg'), - m('div.entrycontent', [ + : null, + m('div.entrycontent', { + class: vnode.attrs.media ? '' : 'extrapadding', + }, [ (vnode.attrs.description ? m('.fr-view', m.trust(vnode.attrs.description)) : null), diff --git a/app/widgets/pages.js b/app/widgets/pages.js index f46733b..e6471ac 100644 --- a/app/widgets/pages.js +++ b/app/widgets/pages.js @@ -1,5 +1,3 @@ -const m = require('mithril') - const Pages = { oninit: function(vnode) { this.onpage = vnode.attrs.onpage || function() {} diff --git a/config/config.default.json b/config/config.default.json index a4d31c4..8e8e998 100644 --- a/config/config.default.json +++ b/config/config.default.json @@ -4,6 +4,7 @@ "port": 4030, "host": "0.0.0.0" }, + "CIRCLECI_VERSION": "circleci_version_number", "knex": { "client": "pg", "connection": { diff --git a/config/config.default.json.org b/config/config.default.json.org new file mode 100644 index 0000000..8e8e998 --- /dev/null +++ b/config/config.default.json.org @@ -0,0 +1,49 @@ +{ + "NODE_ENV": "development", + "server": { + "port": 4030, + "host": "0.0.0.0" + }, + "CIRCLECI_VERSION": "circleci_version_number", + "knex": { + "client": "pg", + "connection": { + "host" : "127.0.0.1", + "user" : "postgres", + "password" : "postgres", + "database" : "nfpmoe" + }, + "connectionslave": null, + "migrations": { + }, + "acquireConnectionTimeout": 10000 + }, + "bunyan": { + "name": "nfpmoe", + "streams": [{ + "stream": "process.stdout", + "level": "debug" + } + ] + }, + "frontend": { + "url": "http://localhost:8080" + }, + "jwt": { + "secret": "this-is-my-secret", + "options": { + "expiresIn": 604800 + } + }, + "googleid": "1076074914074-3no1difo1jq3dfug3glfb25pn1t8idud.apps.googleusercontent.com", + "sessionsecret": "this-is-session-secret-lol", + "bcrypt": 5, + "fileSize": 524288000, + "upload": { + "baseurl": "http://192.168.42.14", + "port": "2111", + "host": "storage01.nfp.is", + "name": "nfpmoe-dev", + "secret": "nfpmoe-dev" + } +} diff --git a/package.json b/package.json index 42c5d0d..de481a4 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,15 @@ "scripts": { "lint": "eslint .", "start": "node --experimental-modules index.mjs", - "build": "sass -s compressed app/app.scss public/assets/app.css && browserify -p tinyify --no-commondir -o public/assets/app.js app/index.js", + "build": "sass -s compressed app/app.scss public/assets/app.css && sass -s compressed app/admin.scss public/assets/admin.css && browserify -p tinyify --no-commondir -o public/assets/app.js app/index.js && browserify -p tinyify --no-commondir -o public/assets/admin.js app/admin.js", "build:check": "browserify -o public/assets/app.js app/index.js", "test": "echo \"Error: no test specified\" && exit 1", "watch:api": "nodemon --experimental-modules index.mjs | bunyan", - "watch:app": "watchify -g envify -d app/index.js -o public/assets/app.js", - "watch:sass": "sass --watch app/app.scss public/assets/app.css", - "dev": "run-p watch:api watch:app watch:sass", + "watch:app:admin": "watchify -g envify -d app/admin.js -o public/assets/admin.js", + "watch:app:public": "watchify -g envify -d app/index.js -o public/assets/app.js", + "watch:sass:public": "sass --watch app/app.scss public/assets/app.css", + "watch:sass:admin": "sass --watch app/admin.scss public/assets/admin.css", + "dev": "run-p watch:api watch:app:public watch:app:admin watch:sass:public watch:sass:admin", "prod": "npm run build && npm start", "docker": "docker run -it --rm --name nfp_moe -e knex__connection__host -e NODE_ENV -p 4030:4030 -v \"$PWD\":/usr/src/app -w /usr/src/app node", "docker:install": "npm run docker -- npm install", @@ -47,6 +49,7 @@ "bcrypt": "^3.0.0", "bookshelf": "^0.15.1", "bunyan-lite": "^1.0.1", + "dot": "^1.1.2", "format-link-header": "^2.1.0", "googleapis": "^42.0.0", "http-errors": "^1.7.2", diff --git a/public/assets/admin.css b/public/assets/admin.css new file mode 100644 index 0000000..fb8d39b --- /dev/null +++ b/public/assets/admin.css @@ -0,0 +1 @@ +.error{font-size:.8em;color:#bb4d00;font-weight:bold;padding-bottom:20px}.admin-wrapper table{width:calc(100% - 20px);margin:10px;border:solid 1px #01579b;border-collapse:collapse;border-spacing:0;font-size:.8em}.admin-wrapper table thead th{background-color:#3d77c7;border:solid 1px #01579b;color:#fff;padding:10px;text-align:left}.admin-wrapper table tbody td{text-align:left;border:solid 1px #01579b;color:#333;padding:10px}.admin-wrapper table a,.admin-wrapper table a:visited,.admin-wrapper table a:hover{text-decoration:none;color:#bb4d00;font-weight:bold}.admin-wrapper table button{color:#bb4d00;background:transparent;border:1px solid #bb4d00}.admin-wrapper table td.right,.admin-wrapper table th.right{text-align:right}.floating-container{position:fixed;top:0;bottom:0;left:0;right:0;background:#00000099;display:flex;flex-direction:column;justify-content:center;align-items:center}.admin-wrapper{flex-grow:2;display:flex;flex-direction:column;background:#01579b;padding:0 20px 50px}.admin-actions{background:#01579b;display:flex;justify-content:center;min-height:37px}.admin-actions span{color:#fff;padding:10px;font-size:14px;font-weight:bold}.admin-actions a{padding:10px;text-decoration:none;color:#ffad42;font-size:14px;font-weight:bold}.fr-box,.fr-toolbar,.fr-box .second-toolbar{border-radius:0 !important}article.editpage{text-align:center;background:#fff;padding:0 0 20px}article.editpage header{padding:10px;background:#f57c00}article.editpage header h1{color:#000}article.editpage header a{font-size:14px;text-decoration:none;margin-left:10px;color:#000}article.editpage fileupload{margin:0 0 20px}article.editpage fileupload .inside{height:150px}article.editpage fileupload.cover{align-self:center;min-width:178px}article.editpage fileupload.cover .display{background-size:auto 100%}article.editpage form{padding:0 40px 20px}article.editpage form textarea{height:300px}article.editpage form .loading-spinner{height:300px;position:relative}article.editpage h5{margin-bottom:20px}article.editpage>.loading-spinner{width:240px;height:50px;position:relative}table span.subpage{padding:0 5px}article.editarticle{text-align:center;background:#fff;padding:0 0 20px}article.editarticle header{padding:10px;background:#f57c00}article.editarticle header h1{color:#000}article.editarticle header a{font-size:14px;text-decoration:none;margin-left:10px;color:#000}article.editarticle fileupload{margin:0 0 20px}article.editarticle fileupload .inside{height:150px}article.editarticle fileupload.cover{align-self:center;min-width:178px}article.editarticle form{padding:0 40px 20px}article.editarticle form textarea{height:300px}article.editarticle form .loading-spinner{height:300px;position:relative}article.editarticle h5{margin-bottom:20px}article.editarticle>.loading-spinner{width:240px;height:50px;position:relative}article.editarticle>.loading-spinner.full{width:100%}article.editarticle .fileupload{align-self:center;padding:.5em;margin:.5em 0;min-width:250px;border:none;border:1px solid #f57c00;background:#ffad42;color:#000;position:relative}article.editarticle .fileupload input{position:absolute;top:0;left:0;right:0;bottom:0;opacity:.01;width:100%;cursor:pointer;text-indent:-9999px;z-index:2}article.editarticle files{align-items:stretch;width:100%;display:flex;flex-direction:column;padding:10px 40px 0;text-align:left}article.editarticle files h4{font-size:1.1em;font-weight:bold;padding:0 5px 5px;margin-bottom:10px;border-bottom:1px solid #ccc}table span.subarticle{padding:0 5px}article.editstaff{text-align:center;background:#fff;padding:0 0 20px}article.editstaff header{padding:10px;background:#f57c00}article.editstaff header h1{color:#000}article.editstaff header a{font-size:14px;text-decoration:none;margin-left:10px;color:#000}article.editstaff form{padding:0 40px 20px}article.editstaff form textarea{height:300px}article.editstaff form .loading-spinner{height:300px;position:relative}article.editstaff h5{margin-bottom:20px}article.editstaff>.loading-spinner{width:240px;height:50px;position:relative}article.editstaff>.loading-spinner.full{width:100%}fileupload{position:relative;display:flex;align-items:stretch;flex-direction:column;justify-content:stretch}fileupload .showicon,fileupload .showbordericon,fileupload .display{flex-grow:2}fileupload .showbordericon{border:3px solid #555;border-style:dashed;background-image:url("./img/upload.svg");background-position:center;background-repeat:no-repeat;background-size:50px}fileupload .showicon{position:absolute;top:5px;right:5px;width:50px;height:50px;background-image:url("./img/upload.svg");background-position:center;background-repeat:no-repeat;background-size:contain}fileupload img{max-width:600px;width:100%;align-self:center;min-height:100px}fileupload .display{background-size:cover;background-repeat:no-repeat;background-position:center}fileupload .loading-spinner{position:absolute;top:0;left:0;right:0;bottom:0;background:#33333388;width:100%}fileupload input{position:absolute;top:0;left:0;right:0;bottom:0;opacity:.01;width:100%;cursor:pointer;text-indent:-9999px;z-index:2}fileupload .remove{border:none;position:absolute;top:5px;right:60px;width:50px;height:50px;background-image:url("./img/delete.svg");background-position:center;background-repeat:no-repeat;background-color:transparent;background-size:contain;z-index:3;outline:none;cursor:pointer}dialogue{background:#fff;display:flex;flex-direction:column;text-align:center;width:calc(100% - 40px);max-width:500px;color:#000}dialogue h2{background:#bb4d00;color:#fff;font-size:1.5em;padding:10px}dialogue p{padding:10px}dialogue .buttons{display:flex;justify-content:space-around;padding:10px}dialogue button{border:1px solid #bb4d00;background:transparent;color:#bb4d00;padding:5px 15px;min-width:150px}dialogue button.alert{border-color:red;color:red}dialogue button.cancel{border-color:#999;color:#999}.darkmodeon .maincontainer .admin-wrapper{color:#000}.darkmodeon .error{color:#e05e00}/*# sourceMappingURL=admin.css.map */ diff --git a/public/assets/admin.css.map b/public/assets/admin.css.map new file mode 100644 index 0000000..0fb703b --- /dev/null +++ b/public/assets/admin.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../../app/admin.scss","../../app/_common.scss","../../app/admin/admin.scss","../../app/admin/pages.scss","../../app/admin/articles.scss","../../app/admin/staff.scss","../../app/widgets/admin.scss"],"names":[],"mappings":"AAEA,OACE,eACA,MCSkB,QDRlB,iBACA,oBAOF,qBACE,wBACA,YACA,yBACA,yBACA,iBACA,eAEA,8BACE,iBClBe,QDmBf,yBACA,MCnBe,KDoBf,aACA,gBAEF,8BACE,gBACA,yBACA,MCfO,KDgBP,aAEF,mFAGE,qBACA,MCzBgB,QD0BhB,iBAGF,4BACE,MC9BgB,QD+BhB,uBACA,yBAGF,4DAEE,iBAIJ,oBACE,eACA,MACA,SACA,OACA,QACA,qBACA,aACA,sBACA,uBACA,mBE/DF,eACE,YACA,aACA,sBACA,WDJW,QCKX,oBAGF,eACE,WDTW,QCUX,aACA,uBACA,gBAEA,oBACE,MDdS,KCeT,aACA,eACA,iBAGF,iBACE,aACA,qBACA,MDdiB,QCejB,eACA,iBAIJ,4CAGE,2BCjCF,iBACE,kBACA,gBACA,iBAEA,wBACE,aACA,WFCW,QECX,2BACE,MFDS,KEIX,0BACE,eACA,qBACA,iBACA,MFNe,KEUnB,4BACE,gBAEA,oCACE,aAIJ,kCACE,kBACA,gBAEA,2CACE,0BAIJ,sBACE,oBAEA,+BACE,aAGF,uCACE,aACA,kBAIJ,oBACE,mBAGF,kCACE,YACA,YACA,kBAIJ,mBACE,cC/DF,oBACE,kBACA,gBACA,iBAEA,2BACE,aACA,WHCW,QGCX,8BACE,MHDS,KGIX,6BACE,eACA,qBACA,iBACA,MHNe,KGUnB,+BACE,gBAEA,uCACE,aAIJ,qCACE,kBACA,gBAGF,yBACE,oBAEA,kCACE,aAGF,0CACE,aACA,kBAIJ,uBACE,mBAGF,qCACE,YACA,YACA,kBAEA,0CACE,WAIJ,gCACE,kBACA,aACA,cACA,gBACA,YACA,yBACA,WH1DiB,QG2DjB,MH1DiB,KG2DjB,kBAEA,sCACE,kBACA,MACA,OACA,QACA,SACA,YACA,WACA,eACA,oBACA,UAIJ,0BACE,oBACA,WACA,aACA,sBACA,oBACA,gBAEA,6BACE,gBACA,iBACA,kBACA,mBACA,6BAKN,sBACE,cCzGF,kBACE,kBACA,gBACA,iBAEA,yBACE,aACA,WJCW,QICX,4BACE,MJDS,KIIX,2BACE,eACA,qBACA,iBACA,MJNe,KIUnB,uBACE,oBAEA,gCACE,aAGF,wCACE,aACA,kBAIJ,qBACE,mBAGF,mCACE,YACA,YACA,kBAEA,wCACE,WC5CN,WACE,kBACA,aACA,oBACA,sBACA,wBAEA,oEAGE,YAGF,2BACE,sBACA,oBACA,yCACA,2BACA,4BACA,qBAGF,qBACE,kBACA,QACA,UACA,WACA,YACA,yCACA,2BACA,4BACA,wBAGF,eACE,gBACA,WACA,kBACA,iBAGF,oBACE,sBACA,4BACA,2BAGF,4BACE,kBACA,MACA,OACA,QACA,SACA,qBACA,WAGF,iBACE,kBACA,MACA,OACA,QACA,SACA,YACA,WACA,eACA,oBACA,UAGF,mBACE,YACA,kBACA,QACA,WACA,WACA,YACA,yCACA,2BACA,4BACA,6BACA,wBACA,UACA,aACA,eAIJ,SACE,gBACA,aACA,sBACA,kBACA,wBACA,gBACA,MLvEQ,KKyER,YACE,WLtFgB,QKuFhB,MLtFgB,KKuFhB,gBACA,aAGF,WACE,aAGF,kBACE,aACA,6BACA,aAGF,gBACE,yBACA,uBACA,MLzGgB,QK0GhB,iBACA,gBAGF,sBACE,iBACA,UAGF,uBACE,kBACA,WN3DF,0CACE,MC/CM,KDkDR,mBACE,MC7BqB","file":"admin.css"} \ No newline at end of file diff --git a/public/assets/admin.js b/public/assets/admin.js new file mode 100644 index 0000000..cf9032c --- /dev/null +++ b/public/assets/admin.js @@ -0,0 +1 @@ +!function(){var t={};const e={currentUser:null,isAdmin:!1,loadedGoogle:!1,loadingGoogle:!1,loadingListeners:[],authListeners:[],updateToken:function(t){if(!t)return e.clearToken();localStorage.setItem("logintoken",t),e.currentUser=JSON.parse(atob(t.split(".")[1])),e.authListeners.length&&e.authListeners.forEach(function(t){t(e.currentUser)})},clearToken:function(){e.currentUser=null,localStorage.removeItem("logintoken"),e.isAdmin=!1},addEvent:function(t){e.authListeners.push(t)},setAdmin:function(t){e.isAdmin=t},createGoogleScript:function(){return e.loadedGoogle?Promise.resolve():new Promise(function(t){if(e.loadedGoogle)return t();if(e.loadingListeners.push(t),e.loadingGoogle)return;e.loadingGoogle=!0;let a=document.createElement("script");a.type="text/javascript",a.async=!0,a.defer=!0,a.src="https://apis.google.com/js/platform.js?onload=googleLoaded",document.body.appendChild(a)})},getToken:function(){return localStorage.getItem("logintoken")}};window.googleLoaded||(window.googleLoaded=function(){for(e.loadedGoogle=!0;e.loadingListeners.length;)e.loadingListeners.pop()()}),e.updateToken(localStorage.getItem("logintoken")),t=e;var a={sendRequest:function(e,a){let i=t.getToken(),n=a;return i&&(e.headers=e.headers||{},e.headers.Authorization="Bearer "+i),e.extract=function(t){let e=null;if(n&&t.status<300){let a={};t.getAllResponseHeaders().split("\r\n").forEach(function(t){var e=t.split(": ");a[e[0]]=e[1]}),e={headers:a||{},data:JSON.parse(t.responseText)}}else e=t.responseText?JSON.parse(t.responseText):{};if(t.status>=300)throw e;return e},m.request(e).catch(function(e){return 403===e.code&&(t.clearToken(),m.route.set("/login",{redirect:m.route.get()})),e.response&&e.response.status?Promise.reject(e.response):Promise.reject(e)})}},i={};const{sendRequest:n}=a;i.uploadMedia=function(t){let e=new FormData;return e.append("file",t),n({method:"POST",url:"/api/media",body:e})};const{uploadMedia:r}=i;var s={uploadFile(t,e){e.target.files[0]&&(t.state.updateError(t,""),t.state.loading=!0,r(e.target.files[0]).then(function(e){t.attrs.onupload&&t.attrs.onupload(e)}).catch(function(e){t.state.updateError(t,e.message)}).then(function(){e.target.value=null,t.state.loading=!1,m.redraw()}))},updateError:function(t,e){t.attrs.onerror?t.attrs.onerror(e):t.state.error=e},oninit:function(t){t.state.loading=!1,t.state.error=""},view:function(t){let e=t.attrs.media;return m("fileupload",{class:t.attrs.class||null},[m("div.error",{hidden:!t.state.error},t.state.error),e?t.attrs.useimg?[m("img",{src:e.large_url}),m("div.showicon")]:m("a.display.inside",{href:e.large_url,style:{"background-image":'url("'+e.large_url+'")'}},m("div.showicon")):m("div.inside.showbordericon"),m("input",{accept:"image/*",type:"file",onchange:this.uploadFile.bind(this,t)}),e&&t.attrs.ondelete?m("button.remove",{onclick:t.attrs.ondelete}):null,t.state.loading?m("div.loading-spinner"):null])}};const o={files:[{type:"css",url:"https://cdn.jsdelivr.net/npm/froala-editor@3.0.4/css/froala_editor.pkgd.min.css"},{type:"css",url:"https://cdn.jsdelivr.net/npm/froala-editor@3.0.4/css/themes/gray.min.css"},{type:"js",url:"https://cdn.jsdelivr.net/npm/froala-editor@3.0.4/js/froala_editor.pkgd.min.js"}],loadedFiles:0,loadedFroala:!1,checkLoadedAll:function(t){o.loadedFiles]+data-f-id="pbf"[^>]+>[^>]+>[^>]+>[^>]+>/,"")),this.loading=!0,(a=this.page.id?f(this.page.id,{name:this.page.name,path:this.page.path,parent_id:this.page.parent_id,description:this.page.description,banner_id:this.page.banner&&this.page.banner.id||null,media_id:this.page.media&&this.page.media.id||null}):u({name:this.page.name,path:this.page.path,parent_id:this.page.parent_id,description:this.page.description,banner_id:this.page.banner&&this.page.banner.id||null,media_id:this.page.media&&this.page.media.id||null})).then(function(e){t.state.page.id?(e.media=t.state.page.media,e.banner=t.state.page.banner,t.state.page=e):m.route.set("/admin/pages/"+e.id)}).catch(function(e){t.state.error=e.message}).then(function(){t.state.loading=!1,m.redraw()}),!1},view:function(t){const e=[{id:null,name:"-- Frontpage --"}].concat(g).filter(function(e){return!t.state.page||e.id!==t.state.page.id});return this.loading?m("div.loading-spinner"):m("div.admin-wrapper",[m("div.admin-actions",this.page.id?[m("span","Actions:"),m(m.route.Link,{href:"/page/"+this.page.path},"View page"),m(m.route.Link,{href:"/admin/pages/add"},"Create new page")]:null),m("article.editpage",[m("header",m("h1",this.creating?"Create Page":"Edit "+(this.page.name||"(untitled)"))),m("div.error",{hidden:!this.error,onclick:function(){t.state.error=""}},this.error),m(s,{onupload:this.fileUploaded.bind(this,"banner"),ondelete:this.fileRemoved.bind(this,"banner"),onerror:function(e){t.state.error=e},media:this.page&&this.page.banner}),m(s,{class:"cover",useimg:!0,onupload:this.fileUploaded.bind(this,"media"),ondelete:this.fileRemoved.bind(this,"media"),onerror:function(e){t.state.error=e},media:this.page&&this.page.media}),m("form.editpage.content",{onsubmit:this.save.bind(this,t)},[m("label","Parent"),m("select",{onchange:this.updateParent.bind(this)},e.map(function(e){return m("option",{value:e.id||-1,selected:e.id===t.state.page.parent_id},e.name)})),m("label","Name"),m("input",{type:"text",value:this.page.name,oninput:this.updateValue.bind(this,"name")}),m("label","Description"),this.loadedFroala?m("div",{oncreate:function(e){t.state.froala=new FroalaEditor(e.dom,v.getFroalaOptions(),function(){t.state.froala.html.set(t.state.page.description)})}}):null,m("label","Path"),m("input",{type:"text",value:this.page.path,oninput:this.updateValue.bind(this,"path")}),m("div.loading-spinner",{hidden:this.loadedFroala}),m("input",{type:"submit",value:"Save"})])])])}};var b=v;var y={view:function(t){return m("div.floating-container",{hidden:t.attrs.hidden},m("dialogue",[m("h2",t.attrs.title),m("p",t.attrs.message),m("div.buttons",[m("button",{class:t.attrs.yesclass||"",onclick:t.attrs.onyes},t.attrs.yes),m("button",{class:t.attrs.noclass||"",onclick:t.attrs.onno},t.attrs.no)])]))}};const{getAllPages:w,removePage:k}=h,A={parseTree:function(t){let e=new Map;for(let a=0;a"):null,m(m.route.Link,{href:"/admin/pages/"+e.id},e.name)]),m("td",m(m.route.Link,{href:"/page/"+e.path},"/page/"+e.path)),m("td.right",e.updated_at.replace("T"," ").split(".")[0]),m("td.right",m("button",{onclick:function(){t.state.removePage=e}},"Remove"))])].concat(e.children.map(A.drawPage.bind(this,t)))},view:function(t){return[this.loading?m("div.loading-spinner"):m("div.admin-wrapper",[m("div.admin-actions",[m("span","Actions:"),m(m.route.Link,{href:"/admin/pages/add"},"Create new page")]),m("article.editpage",[m("header",m("h1","All pages")),m("div.error",{hidden:!this.error,onclick:function(){t.state.error=""}},this.error),m("table",[m("thead",m("tr",[m("th","Title"),m("th","Path"),m("th.right","Updated"),m("th.right","Actions")])),m("tbody",this.pages.map(A.drawPage.bind(this,t)))])])]),m(y,{hidden:null===t.state.removePage,title:"Delete "+(t.state.removePage?t.state.removePage.name:""),message:'Are you sure you want to remove "'+(t.state.removePage?t.state.removePage.name:"")+'" ('+(t.state.removePage?t.state.removePage.path:"")+")",yes:"Remove",yesclass:"alert",no:"Cancel",noclass:"cancel",onyes:this.confirmRemovePage.bind(this,t),onno:function(){t.state.removePage=null}})]}};var P=A,x={};const{sendRequest:S}=a;function F(t,e){return Object.prototype.hasOwnProperty.call(t,e)}x.createArticle=function(t){return S({method:"POST",url:"/api/articles",body:t})},x.updateArticle=function(t,e){return S({method:"PUT",url:"/api/articles/"+t,body:e})},x.getAllArticlesPagination=function(t){let e="";return t.sort&&(e+="&sort="+t.sort),t.per_page&&(e+="&perPage="+t.per_page),t.page&&(e+="&page="+t.page),t.includes&&(e+="&includes="+t.includes.join(",")),"/api/articles?"+e},x.getArticle=function(t){return S({method:"GET",url:"/api/articles/"+t+"?includes=media,parent,banner,files"})},x.removeArticle=function(t,e){return S({method:"DELETE",url:"/api/articles/"+e})};var L=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},T=function(t){switch(typeof t){case"string":return t;case"boolean":return t?"true":"false";case"number":return isFinite(t)?t:"";default:return""}},O=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)};function j(t,e){if(t.map)return t.map(e);for(var a=[],i=0;i0&&o>s&&(o=s);for(var l=0;l=0?(h=m.substr(0,f),d=m.substr(f+1)):(h=m,d=""),c=decodeURIComponent(h),u=decodeURIComponent(d),F(n,c)?L(n[c])?n[c].push(u):n[c]=[n[c],u]:n[c]=u}return n},stringify:function(t,e,a,i){return e=e||"&",a=a||"=",null===t&&(t=void 0),"object"==typeof t?j(_(t),function(i){var n=encodeURIComponent(T(i))+a;return O(t[i])?j(t[i],function(t){return n+encodeURIComponent(T(t))}).join(e):n+encodeURIComponent(T(t[i]))}).join(e):i?encodeURIComponent(T(i))+a+encodeURIComponent(T(t)):""}},R={exports:{}};(function(t){!function(e){var a="object"==typeof R.exports&&R.exports&&!R.exports.nodeType&&R.exports,i=R&&!R.nodeType&&R,n="object"==typeof t&&t;n.global!==n&&n.window!==n&&n.self!==n||(e=n);var r,s,o=2147483647,l=36,h=1,d=26,c=38,u=700,m=72,f=128,p="-",g=/^xn--/,v=/[^\x20-\x7E]/,b=/[\x2E\u3002\uFF0E\uFF61]/g,y={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},w=l-h,k=Math.floor,A=String.fromCharCode;function P(t){throw new RangeError(y[t])}function x(t,e){for(var a=t.length,i=[];a--;)i[a]=e(t[a]);return i}function S(t,e){var a=t.split("@"),i="";return a.length>1&&(i=a[0]+"@",t=a[1]),i+x((t=t.replace(b,".")).split("."),e).join(".")}function F(t){for(var e,a,i=[],n=0,r=t.length;n=55296&&e<=56319&&n65535&&(e+=A((t-=65536)>>>10&1023|55296),t=56320|1023&t),e+A(t)}).join("")}function T(t,e){return t+22+75*(t<26)-((0!=e)<<5)}function O(t,e,a){var i=0;for(t=a?k(t/u):t>>1,t+=k(t/e);t>w*d>>1;i+=l)t=k(t/w);return k(i+(w+1)*t/(t+c))}function j(t){var e,a,i,n,r,s,c,u,g,v,b,y=[],w=t.length,A=0,x=f,S=m;for((a=t.lastIndexOf(p))<0&&(a=0),i=0;i=128&&P("not-basic"),y.push(t.charCodeAt(i));for(n=a>0?a+1:0;n=w&&P("invalid-input"),((u=(b=t.charCodeAt(n++))-48<10?b-22:b-65<26?b-65:b-97<26?b-97:l)>=l||u>k((o-A)/s))&&P("overflow"),A+=u*s,!(u<(g=c<=S?h:c>=S+d?d:c-S));c+=l)s>k(o/(v=l-g))&&P("overflow"),s*=v;S=O(A-r,e=y.length+1,0==r),k(A/e)>o-x&&P("overflow"),x+=k(A/e),A%=e,y.splice(A++,0,x)}return L(y)}function _(t){var e,a,i,n,r,s,c,u,g,v,b,y,w,x,S,L=[];for(y=(t=F(t)).length,e=f,a=0,r=m,s=0;s=e&&bk((o-a)/(w=i+1))&&P("overflow"),a+=(c-e)*w,e=c,s=0;so&&P("overflow"),b==e){for(u=a,g=l;!(u<(v=g<=r?h:g>=r+d?d:g-r));g+=l)S=u-v,x=l-v,L.push(A(T(v+S%x,0))),u=k(S/x);L.push(A(T(u,0))),r=O(a,w,i==n),a=0,++i}++a,++e}return L.join("")}if(r={version:"1.4.1",ucs2:{decode:F,encode:L},decode:j,encode:_,toASCII:function(t){return S(t,function(t){return v.test(t)?"xn--"+_(t):t})},toUnicode:function(t){return S(t,function(t){return g.test(t)?j(t.slice(4).toLowerCase()):t})}},"function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return r});else if(a&&i)if(R.exports==a)i.exports=r;else for(s in r)r.hasOwnProperty(s)&&(a[s]=r[s]);else e.punycode=r}(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{}),R=R.exports;var U={isString:function(t){return"string"==typeof t},isObject:function(t){return"object"==typeof t&&null!==t},isNull:function(t){return null===t},isNullOrUndefined:function(t){return null==t}},C={};function q(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}C.parse=K;var D=/^([a-z0-9.+-]+:)/i,I=/:[0-9]*$/,M=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,N=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),G=["'"].concat(N),B=["%","/","?",";","#"].concat(G),V=["/","?","#"],z=/^[+a-z0-9A-Z_-]{0,63}$/,H=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,$={javascript:!0,"javascript:":!0},J={javascript:!0,"javascript:":!0},Z={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};function K(t,e,a){if(t&&U.isObject(t)&&t instanceof q)return t;var i=new q;return i.parse(t,e,a),i}q.prototype.parse=function(t,e,a){if(!U.isString(t))throw new TypeError("Parameter 'url' must be a string, not "+typeof t);var i=t.indexOf("?"),n=-1!==i&&i127?w+="x":w+=y[k];if(!w.match(z)){var P=v.slice(0,f),x=v.slice(f+1),S=y.match(H);S&&(P.push(S[1]),x.unshift(S[2])),x.length&&(s="/"+x.join(".")+s),this.hostname=P.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),g||(this.hostname=R.toASCII(this.hostname));var F=this.port?":"+this.port:"",L=this.hostname||"";this.host=L+F,this.href+=this.host,g&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==s[0]&&(s="/"+s))}if(!$[h])for(f=0,b=G.length;f0)&&a.host.split("@"))&&(a.auth=S.shift(),a.host=a.hostname=S.shift())),a.search=t.search,a.query=t.query,U.isNull(a.pathname)&&U.isNull(a.search)||(a.path=(a.pathname?a.pathname:"")+(a.search?a.search:"")),a.href=a.format(),a;if(!y.length)return a.pathname=null,a.search?a.path="/"+a.search:a.path=null,a.href=a.format(),a;for(var k=y.slice(-1)[0],A=(a.host||t.host||y.length>1)&&("."===k||".."===k)||""===k,P=0,x=y.length;x>=0;x--)"."===(k=y[x])?y.splice(x,1):".."===k?(y.splice(x,1),P++):P&&(y.splice(x,1),P--);if(!v&&!b)for(;P--;P)y.unshift("..");!v||""===y[0]||y[0]&&"/"===y[0].charAt(0)||y.unshift(""),A&&"/"!==y.join("/").substr(-1)&&y.push("");var S,F=""===y[0]||y[0]&&"/"===y[0].charAt(0);return w&&(a.hostname=a.host=F?"":y.length?y.shift():"",(S=!!(a.host&&a.host.indexOf("@")>0)&&a.host.split("@"))&&(a.auth=S.shift(),a.host=a.hostname=S.shift())),(v=v||a.host&&y.length)&&!F&&y.unshift(""),y.length?a.pathname=y.join("/"):(a.pathname=null,a.path=null),U.isNull(a.pathname)&&U.isNull(a.search)||(a.path=(a.pathname?a.pathname:"")+(a.search?a.search:"")),a.auth=t.auth||a.auth,a.slashes=a.slashes||t.slashes,a.href=a.format(),a},q.prototype.parseHost=function(){var t=this.host,e=I.exec(t);e&&(":"!==(e=e[0])&&(this.port=e.substr(1)),t=t.substr(0,t.length-e.length)),t&&(this.hostname=t)};var W=function(){for(var t={},e=0;e]*)>(.*)/),a=e[1],i=e[2].split(";"),n=C.parse(a),r=E.parse(n.query);i.shift();var s=i.reduce(tt,{});return(s=W(r,s)).url=a,s}catch(o){return null}}var at=function(t){return t?t.split(/,\s*({data:t.data,links:at(t.headers.link||""),total:Number(t.headers.pagination_total||"0")}))};var rt={oninit:function(t){this.onpage=t.attrs.onpage||function(){}},view:function(t){return t.attrs.links?m("pages",[t.attrs.links.first?m(m.route.Link,{href:t.attrs.base+"?page="+t.attrs.links.first.page,onclick:function(){t.state.onpage(t.attrs.links.first.page)}},"First"):m("div"),t.attrs.links.previous?m(m.route.Link,{href:t.attrs.base+"?page="+t.attrs.links.previous.page,onclick:function(){t.state.onpage(t.attrs.links.previous.page)}},t.attrs.links.previous.title):m("div"),m("div",t.attrs.links.current&&t.attrs.links.current.title||"Current page"),t.attrs.links.next?m(m.route.Link,{href:t.attrs.base+"?page="+t.attrs.links.next.page,onclick:function(){t.state.onpage(t.attrs.links.next.page)}},t.attrs.links.next.title):m("div"),t.attrs.links.last?m(m.route.Link,{href:t.attrs.base+"?page="+t.attrs.links.last.page,onclick:function(){t.state.onpage(t.attrs.links.last.page)}},"Last"):m("div")]):null}};const{getAllArticlesPagination:st,removeArticle:ot}=x,{fetchPage:lt}=it,ht={oninit:function(t){this.error="",this.lastpage=m.route.param("page")||"1",this.articles=[],this.removeArticle=null,this.fetchArticles(t)},onupdate:function(t){m.route.param("page")&&m.route.param("page")!==this.lastpage&&this.fetchArticles(t)},fetchArticles:function(t){return this.loading=!0,this.links=null,this.lastpage=m.route.param("page")||"1",lt(st({per_page:10,page:this.lastpage,includes:["parent"]})).then(function(e){t.state.articles=e.data,t.state.links=e.links}).catch(function(e){t.state.error=e.message}).then(function(){t.state.loading=!1,m.redraw()})},confirmRemoveArticle:function(t){let e=this.removeArticle;this.removeArticle=null,this.loading=!0,ot(e,e.id).then(this.oninit.bind(this,t)).catch(function(e){t.state.error=e.message,t.state.loading=!1,m.redraw()})},drawArticle:function(t,e){let a;return a=e.parent?{path:"/page/"+e.parent.path,name:e.parent.name}:{path:"/",name:"-- Frontpage --"},[m("tr",[m("td",m(m.route.Link,{href:"/admin/articles/"+e.id},e.name)),m("td",m(m.route.Link,{href:a.path},a.name)),m("td",m(m.route.Link,{href:"/article/"+e.path},"/article/"+e.path)),m("td.right",e.updated_at.replace("T"," ").split(".")[0]),m("td.right",m("button",{onclick:function(){t.state.removeArticle=e}},"Remove"))])]},view:function(t){return[m("div.admin-wrapper",[m("div.admin-actions",[m("span","Actions:"),m(m.route.Link,{href:"/admin/articles/add"},"Create new article")]),m("article.editarticle",[m("header",m("h1","All articles")),m("div.error",{hidden:!this.error,onclick:function(){t.state.error=""}},this.error),this.loading?m("div.loading-spinner.full"):m("table",[m("thead",m("tr",[m("th","Title"),m("th","Page"),m("th","Path"),m("th.right","Updated"),m("th.right","Actions")])),m("tbody",this.articles.map(ht.drawArticle.bind(this,t)))]),m(rt,{base:"/admin/articles",links:this.links})])]),m(y,{hidden:null===t.state.removeArticle,title:"Delete "+(t.state.removeArticle?t.state.removeArticle.name:""),message:'Are you sure you want to remove "'+(t.state.removeArticle?t.state.removeArticle.name:"")+'" ('+(t.state.removeArticle?t.state.removeArticle.path:"")+")",yes:"Remove",yesclass:"alert",no:"Cancel",noclass:"cancel",onyes:this.confirmRemoveArticle.bind(this,t),onno:function(){t.state.removeArticle=null}})]}};var dt=ht,ct={};const{sendRequest:ut}=a;ct.uploadFile=function(t,e){let a=new FormData;return a.append("file",e),ut({method:"POST",url:"/api/articles/"+t+"/file",body:a})};const mt={getPrefix:t=>t.attrs.file.filename.endsWith(".torrent")?t.attrs.file.filename.indexOf("720 ")>=0?"720p":t.attrs.file.filename.indexOf("1080 ")>=0?"1080p":t.attrs.file.filename.indexOf("480 ")>=0?"480p":"Other":t.attrs.file.filename.split(".").slice(-1),getTitle:t=>t.attrs.file.meta.torrent?t.attrs.file.meta.torrent.name:t.attrs.file.filename,getDownloadName:t=>t.attrs.file.meta.torrent?"Torrent":"Download",getSize(t){var e=t,a=-1;do{e/=1024,a++}while(e>1024);return Math.max(e,.1).toFixed(1)+[" kB"," MB"," GB"," TB","PB","EB","ZB","YB"][a]},view:function(t){return m("fileinfo",{class:t.attrs.slim?"slim":""},[m("div.filetitle",[m("span.prefix",this.getPrefix(t)+":"),m("a",{target:"_blank",rel:"noopener",href:t.attrs.file.url},this.getDownloadName(t)),t.attrs.file.magnet?m("a",{href:t.attrs.file.magnet},"Magnet"):null,m("span",this.getTitle(t))]),t.attrs.file.meta.torrent&&!t.attrs.slim?m("ul",t.attrs.file.meta.torrent.files.map(function(t){return m("li",[t.name+" ",m("span.meta","("+mt.getSize(t.size)+")")])})):null])}};var ft=mt;const{Tree:pt}=h,{uploadFile:gt}=ct,{createArticle:vt,updateArticle:bt,getArticle:yt}=x,wt={getFroalaOptions:function(){return{theme:"gray",heightMin:150,videoUpload:!1,imageUploadURL:"/api/media",imageManagerLoadURL:"/api/media",imageManagerDeleteMethod:"DELETE",imageManagerDeleteURL:"/api/media",events:{"imageManager.beforeDeleteImage":function(t){this.opts.imageManagerDeleteURL="/api/media/"+t.data("id")}},requestHeaders:{Authorization:"Bearer "+t.getToken()}}},oninit:function(t){this.froala=null,this.loadedFroala=l.loadedFroala,this.loadedFroala||l.createFroalaScript().then(function(){t.state.loadedFroala=!0,m.redraw()}),this.fetchArticle(t)},onupdate:function(t){this.lastid!==m.route.param("id")&&this.fetchArticle(t)},fetchArticle:function(t){this.lastid=m.route.param("id"),this.loading="add"!==this.lastid,this.creating="add"===this.lastid,this.loadingFile=!1,this.error="",this.article={name:"",path:"",description:"",media:null,banner:null,files:[]},this.editedPath=!1,this.froala=null,this.loadedFroala=l.loadedFroala,"add"!==this.lastid&&yt(this.lastid).then(function(e){t.state.editedPath=!0,t.state.article=e}).catch(function(e){t.state.error=e.message}).then(function(){t.state.loading=!1,m.redraw()})},updateValue:function(t,e){this.article[t]=e.currentTarget.value,"path"===t?this.editedPath=!0:"name"!==t||this.editedPath||(this.article.path=this.article.name.toLowerCase().replace(/ /g,"-"))},updateParent:function(t){this.article.parent_id=Number(t.currentTarget.value),-1===this.article.parent_id&&(this.article.parent_id=null)},mediaUploaded:function(t,e){this.article[t]=e},mediaRemoved:function(t){this.article[t]=null},save:function(t,e){if(e.preventDefault(),this.article.name?this.article.path?this.error="":this.error="Path is missing":this.error="Name is missing",this.error)return;let a;this.article.description=t.state.froala&&t.state.froala.html.get()||this.article.description,this.article.description&&(this.article.description=this.article.description.replace(/]+data-f-id="pbf"[^>]+>[^>]+>[^>]+>[^>]+>/,"")),this.loading=!0,(a=this.article.id?bt(this.article.id,{name:this.article.name,path:this.article.path,parent_id:this.article.parent_id,description:this.article.description,banner_id:this.article.banner&&this.article.banner.id,media_id:this.article.media&&this.article.media.id}):vt({name:this.article.name,path:this.article.path,parent_id:this.article.parent_id,description:this.article.description,banner_id:this.article.banner&&this.article.banner.id,media_id:this.article.media&&this.article.media.id})).then(function(e){t.state.article.id?(e.media=t.state.article.media,e.banner=t.state.article.banner,e.files=t.state.article.files,t.state.article=e):m.route.set("/admin/articles/"+e.id)}).catch(function(e){t.state.error=e.message}).then(function(){t.state.loading=!1,m.redraw()})},uploadFile(t,e){e.target.files[0]&&(t.state.error="",t.state.loadingFile=!0,gt(this.article.id,e.target.files[0]).then(function(e){t.state.article.files.push(e)}).catch(function(e){t.state.error=e.message}).then(function(){e.target.value=null,t.state.loadingFile=!1,m.redraw()}))},getFlatTree:function(){let t=[{id:null,name:"-- Frontpage --"}];return pt.forEach(function(e){t.push({id:e.id,name:e.name}),e.children.length&&e.children.forEach(function(a){t.push({id:a.id,name:e.name+" -> "+a.name})})}),t},view:function(t){const e=this.getFlatTree();return this.loading?m("div.loading-spinner"):m("div.admin-wrapper",[m("div.admin-actions",this.article.id?[m("span","Actions:"),m(m.route.Link,{href:"/article/"+this.article.path},"View article")]:null),m("article.editarticle",[m("header",m("h1",this.creating?"Create Article":"Edit "+(this.article.name||"(untitled)"))),m("div.error",{hidden:!this.error,onclick:function(){t.state.error=""}},this.error),m(s,{onupload:this.mediaUploaded.bind(this,"banner"),onerror:function(e){t.state.error=e},ondelete:this.mediaRemoved.bind(this,"banner"),media:this.article&&this.article.banner}),m(s,{class:"cover",useimg:!0,onupload:this.mediaUploaded.bind(this,"media"),ondelete:this.mediaRemoved.bind(this,"media"),onerror:function(e){t.state.error=e},media:this.article&&this.article.media}),m("form.editarticle.content",{onsubmit:this.save.bind(this,t)},[m("label","Parent"),m("select",{onchange:this.updateParent.bind(this)},e.map(function(e){return m("option",{value:e.id||-1,selected:e.id===t.state.article.parent_id},e.name)})),m("label","Name"),m("input",{type:"text",value:this.article.name,oninput:this.updateValue.bind(this,"name")}),m("label","Description"),this.loadedFroala?m("div",{oncreate:function(e){t.state.froala=new FroalaEditor(e.dom,wt.getFroalaOptions(),function(){t.state.froala.html.set(t.state.article.description)})}}):null,m("label","Path"),m("input",{type:"text",value:this.article.path,oninput:this.updateValue.bind(this,"path")}),m("div.loading-spinner",{hidden:this.loadedFroala}),m("input",{type:"submit",value:"Save"})]),this.article.files.length?m("files",[m("h4","Files"),this.article.files.map(function(t){return m(ft,{file:t})})]):null,this.article.id?m("div.fileupload",["Add file",m("input",{accept:"*",type:"file",onchange:this.uploadFile.bind(this,t)}),t.state.loadingFile?m("div.loading-spinner"):null]):null])])}};var kt=wt,At={};const{sendRequest:Pt}=a;At.createStaff=function(t){return Pt({method:"POST",url:"/api/staff",body:t})},At.updateStaff=function(t,e){return Pt({method:"PUT",url:"/api/staff/"+t,body:e})},At.getAllStaff=function(){return Pt({method:"GET",url:"/api/staff"})},At.getStaff=function(t){return Pt({method:"GET",url:"/api/staff/"+t})},At.removeStaff=function(t){return Pt({method:"DELETE",url:"/api/staff/"+t})};const{getAllStaff:xt,removeStaff:St}=At,Ft={oninit:function(t){this.error="",this.lastpage=m.route.param("page")||"1",this.staff=[],this.removeStaff=null,this.fetchStaffs(t)},fetchStaffs:function(t){return this.loading=!0,xt().then(function(e){t.state.staff=e}).catch(function(e){t.state.error=e.message}).then(function(){t.state.loading=!1,m.redraw()})},confirmRemoveStaff:function(t){let e=this.removeStaff;this.removeStaff=null,this.loading=!0,St(e.id).then(this.oninit.bind(this,t)).catch(function(e){t.state.error=e.message,t.state.loading=!1,m.redraw()})},getLevel:function(t){return 100===t?"Admin":"Manager"},view:function(t){return[m("div.admin-wrapper",[m("div.admin-actions",[m("span","Actions:"),m(m.route.Link,{href:"/admin/staff/add"},"Create new staff")]),m("article.editarticle",[m("header",m("h1","All staff")),m("div.error",{hidden:!this.error,onclick:function(){t.state.error=""}},this.error),this.loading?m("div.loading-spinner.full"):m("table",[m("thead",m("tr",[m("th","Fullname"),m("th","Email"),m("th","Level"),m("th.right","Updated"),m("th.right","Actions")])),m("tbody",this.staff.map(function(e){return m("tr",[m("td",m(m.route.Link,{href:"/admin/staff/"+e.id},e.fullname)),m("td",e.email),m("td.right",Ft.getLevel(e.level)),m("td.right",(e.updated_at||"---").replace("T"," ").split(".")[0]),m("td.right",m("button",{onclick:function(){t.state.removeStaff=e}},"Remove"))])}))]),m(rt,{base:"/admin/staff",links:this.links})])]),m(y,{hidden:null===t.state.removeStaff,title:"Delete "+(t.state.removeStaff?t.state.removeStaff.name:""),message:'Are you sure you want to remove "'+(t.state.removeStaff?t.state.removeStaff.fullname:"")+'" ('+(t.state.removeStaff?t.state.removeStaff.email:"")+")",yes:"Remove",yesclass:"alert",no:"Cancel",noclass:"cancel",onyes:this.confirmRemoveStaff.bind(this,t),onno:function(){t.state.removeStaff=null}})]}};var Lt=Ft;const{createStaff:Tt,updateStaff:Ot,getStaff:jt}=At;var _t={oninit:function(t){this.fetchStaff(t)},onupdate:function(t){this.lastid!==m.route.param("id")&&this.fetchStaff(t)},fetchStaff:function(t){this.lastid=m.route.param("id"),this.loading="add"!==this.lastid,this.creating="add"===this.lastid,this.error="",this.staff={fullname:"",email:"",password:"",level:10},"add"!==this.lastid&&jt(this.lastid).then(function(e){t.state.editedPath=!0,t.state.staff=e}).catch(function(e){t.state.error=e.message}).then(function(){t.state.loading=!1,m.redraw()})},updateValue:function(t,e){this.staff[t]=e.currentTarget.value},save:function(t,e){if(e.preventDefault(),this.staff.fullname?this.staff.email?this.error="":this.error="Email is missing":this.error="Fullname is missing",this.error)return;let a;this.staff.description=t.state.froala&&t.state.froala.html.get()||this.staff.description,this.loading=!0,(a=this.staff.id?Ot(this.staff.id,{fullname:this.staff.fullname,email:this.staff.email,level:this.staff.level,password:this.staff.password}):Tt({fullname:this.staff.fullname,email:this.staff.email,level:this.staff.level,password:this.staff.password})).then(function(t){m.route.set("/admin/staff")}).catch(function(e){t.state.error=e.message}).then(function(){t.state.loading=!1,m.redraw()})},updateLevel:function(t){this.staff.level=Number(t.currentTarget.value)},view:function(t){return this.loading?m("div.loading-spinner"):m("div.admin-wrapper",[m("div.admin-actions",this.staff.id?[m("span","Actions:"),m(m.route.Link,{href:"/admin/staff"},"Staff list")]:null),m("article.editstaff",[m("header",m("h1",this.creating?"Create Staff":"Edit "+(this.staff.fullname||"(untitled)"))),m("div.error",{hidden:!this.error,onclick:function(){t.state.error=""}},this.error),m("form.editstaff.content",{onsubmit:this.save.bind(this,t)},[m("label","Level"),m("select",{onchange:this.updateLevel.bind(this)},[[10,"Manager"],[100,"Admin"]].map(function(e){return m("option",{value:e[0],selected:e[0]===t.state.staff.level},e[1])})),m("label","Fullname"),m("input",{type:"text",value:this.staff.fullname,oninput:this.updateValue.bind(this,"fullname")}),m("label","Email"),m("input",{type:"text",value:this.staff.email,oninput:this.updateValue.bind(this,"email")}),m("label","Password (optional)"),m("input",{type:"text",value:this.staff.password,oninput:this.updateValue.bind(this,"password")}),m("input",{type:"submit",value:"Save"})])])])}};window.addAdminRoutes=[["/admin/pages",P],["/admin/pages/:key",b],["/admin/articles",dt],["/admin/articles/:id",kt],["/admin/staff",Lt],["/admin/staff/:id",_t]]}(); \ No newline at end of file diff --git a/public/index.html b/public/index.html index a481ad0..92b521b 100644 --- a/public/index.html +++ b/public/index.html @@ -6,20 +6,22 @@ - + + - +
- +