Big Optimisations and such

master
Jonatan Nilsson 2019-10-01 03:45:44 +00:00
parent 4f529e22dd
commit eaf2edd6c3
46 changed files with 842 additions and 395 deletions

View File

@ -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} .

View File

@ -8,7 +8,8 @@
},
"globals": {
"FroalaEditor": "readonly",
"gapi": "readonly"
"gapi": "readonly",
"m": true
},
"extends": "eslint:recommended",
"env": {

View File

@ -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

View File

@ -44,7 +44,7 @@ const Media = bookshelf.createModel({
},
url() {
return `${Media.baseUrl}${this.get('large_image')}`
return `${Media.baseUrl}${this.get('medium_image')}`
},
thumb() {

View File

@ -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

View File

@ -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)
}
})
}

15
app/admin.js Normal file
View File

@ -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],
]

79
app/admin.scss Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -1,5 +1,3 @@
const m = require('mithril')
const { getAllArticlesPagination, removeArticle } = require('../api/article')
const { fetchPage } = require('../api/pagination')
const Dialogue = require('../widgets/dialogue')

View File

@ -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(/<p[^>]+data-f-id="pbf"[^>]+>[^>]+>[^>]+>[^>]+>/, '')
}
this.loading = true

View File

@ -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(/<p[^>]+data-f-id="pbf"[^>]+>[^>]+>[^>]+>[^>]+>/, '')
}
this.loading = true

View File

@ -1,5 +1,3 @@
const m = require('mithril')
const { createStaff, updateStaff, getStaff } = require('../api/staff')
const EditStaff = {

View File

@ -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

View File

@ -1,6 +1,3 @@
const m = require('mithril')
const Authentication = require('../authentication')
const { getAllPages, removePage } = require('../api/page')
const Dialogue = require('../widgets/dialogue')

View File

@ -1,5 +1,3 @@
const m = require('mithril')
const { getAllStaff, removeStaff } = require('../api/staff')
const Dialogue = require('../widgets/dialogue')
const Pages = require('../widgets/pages')

View File

@ -1,4 +1,3 @@
const m = require('mithril')
const Authentication = require('../authentication')
exports.sendRequest = function(options, isPagination) {

View File

@ -1,4 +1,3 @@
const m = require('mithril')
const { sendRequest } = require('./common')
exports.uploadMedia = function(file) {

View File

@ -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)',

File diff suppressed because one or more lines are too long

View File

@ -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,

View File

@ -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

View File

@ -10,7 +10,7 @@ const Darkmode = {
Darkmode.darkIsOn = true
} else {
localStorage.removeItem(storageName)
document.body.className = ''
document.body.className = 'daymode'
Darkmode.darkIsOn = false
}
},

View File

@ -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'),
]
},
}

View File

@ -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;
}
}

208
app/froala.scss Normal file
View File

@ -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 }
}

View File

@ -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

View File

@ -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%;

View File

@ -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)
}

View File

@ -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')

View File

@ -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 {

133
app/widgets/admin.scss Normal file
View File

@ -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;
}
}

View File

@ -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;

View File

@ -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),
])
]),
])
)
},

View File

@ -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

View File

@ -1,4 +1,3 @@
const m = require('mithril')
const { uploadMedia } = require('../api/media')
const FileUpload = {

View File

@ -1,4 +1,3 @@
const m = require('mithril')
const Fileinfo = require('./fileinfo')
const Newsentry = {

View File

@ -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),

View File

@ -1,5 +1,3 @@
const m = require('mithril')
const Pages = {
oninit: function(vnode) {
this.onpage = vnode.attrs.onpage || function() {}

View File

@ -4,6 +4,7 @@
"port": 4030,
"host": "0.0.0.0"
},
"CIRCLECI_VERSION": "circleci_version_number",
"knex": {
"client": "pg",
"connection": {

View File

@ -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"
}
}

View File

@ -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",

1
public/assets/admin.css Normal file

File diff suppressed because one or more lines are too long

View File

@ -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"}

1
public/assets/admin.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -6,20 +6,22 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="Stylesheet" href="/assets/app.css?v=4" type="text/css" />
<link rel="Stylesheet" href="/assets/app.css?v={{=it.v}}" type="text/css" />
<link rel="preconnect" href="https://cdn-nfp.global.ssl.fastly.net" />
<meta name="google-signin-client_id" content="1076074914074-3no1difo1jq3dfug3glfb25pn1t8idud.apps.googleusercontent.com">
</head>
<body>
<body class="daymode">
<script type="text/javascript">
if (localStorage.getItem('darkmode')) {
document.body.className = 'darkmodeon'
}
if (localStorage.getItem('darkmode')) {document.body.className = 'darkmodeon';}
window.__nfptree = {{=it.tree}};
window.__nfpdata = {{=it.data}};
window.__nfplinks = {{=it.links}};
</script>
<div class="maincontainer">
<div id="nav"></div>
<main id="main"></main>
<footer id="footer"></footer>
</div>
<script type="text/javascript" src="/assets/app.js?v=4"></script>
<script type="text/javascript" src="/assets/app.js?v={{=it.v}}"></script>
</body>
</html>