Many updates, made server smarter by giving data and added meta tags
This commit is contained in:
parent
0f8cd7ac1a
commit
5b0e9d1d2d
29 changed files with 381 additions and 153 deletions
|
@ -22,7 +22,7 @@
|
|||
"require-await": 0,
|
||||
"array-callback-return": 2,
|
||||
"block-scoped-var": 2,
|
||||
"complexity": ["error", 20],
|
||||
"complexity": ["error", 30],
|
||||
"eqeqeq": [2, "smart"],
|
||||
"no-else-return": ["error", { "allowElseIf": false }],
|
||||
"no-extra-bind": 2,
|
||||
|
|
|
@ -14,7 +14,7 @@ export function accessChecks(opts = { }) {
|
|||
|
||||
export function restrict(level = orgAccess.Normal) {
|
||||
return async (ctx, next) => {
|
||||
if (!ctx.headers.authorization) {
|
||||
if (!ctx.headers.authorization && !ctx.query.token) {
|
||||
return ctx.throw(403, 'Authentication token was not found (did you forget to login?)')
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,8 @@ export function restrict(level = orgAccess.Normal) {
|
|||
return ctx.throw(403, 'You do not have enough access to access this resource')
|
||||
}
|
||||
|
||||
return next()
|
||||
if (next) {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,13 +77,13 @@ const Article = bookshelf.createModel({
|
|||
})
|
||||
},
|
||||
|
||||
getFrontpageArticles() {
|
||||
getFrontpageArticles(page = 1) {
|
||||
return this.query(qb => {
|
||||
qb.orderBy('updated_at', 'DESC')
|
||||
})
|
||||
.fetchPage({
|
||||
pageSize: 10,
|
||||
page: 1,
|
||||
page: page,
|
||||
withRelated: ['files', 'media', 'banner'],
|
||||
})
|
||||
},
|
||||
|
|
|
@ -52,6 +52,15 @@ export default class Jwt {
|
|||
|
||||
static jwtMiddleware() {
|
||||
return koaJwt({
|
||||
getToken: ctx => {
|
||||
if (ctx.request.header.authorization) {
|
||||
return ctx.request.header.authorization.split(' ')[1]
|
||||
}
|
||||
if (ctx.query.token) {
|
||||
return ctx.query.token
|
||||
}
|
||||
return null
|
||||
},
|
||||
secret: (header, payload) =>
|
||||
`${config.get('jwt:secret')}${payload.email}`,
|
||||
passthrough: true,
|
||||
|
|
|
@ -24,12 +24,12 @@ export default class Resizer {
|
|||
.then(() => output)
|
||||
}
|
||||
|
||||
createMedium(input) {
|
||||
createMedium(input, height) {
|
||||
let output = this.Media.getSubUrl(input, 'medium')
|
||||
|
||||
return this.sharp(input)
|
||||
.resize(700, 700, {
|
||||
fit: sharp.fit.inside,
|
||||
.resize(700, height || 700, {
|
||||
fit: height && sharp.fit.cover || sharp.fit.inside,
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.jpeg({
|
||||
|
|
|
@ -19,8 +19,13 @@ export default class MediaRoutes {
|
|||
async upload(ctx) {
|
||||
let result = await this.multer.processBody(ctx)
|
||||
|
||||
let height = null
|
||||
if (ctx.query.height) {
|
||||
height = Number(ctx.query.height)
|
||||
}
|
||||
|
||||
let smallPath = await this.resize.createSmall(result.path)
|
||||
let mediumPath = await this.resize.createMedium(result.path)
|
||||
let mediumPath = await this.resize.createMedium(result.path, height)
|
||||
let largePath = await this.resize.createLarge(result.path)
|
||||
|
||||
let token = this.jwt.signDirect({ site: config.get('upload:name') }, config.get('upload:secret'))
|
||||
|
|
|
@ -1,87 +1,15 @@
|
|||
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'
|
||||
import access from './access/index.mjs'
|
||||
import { restrict } from './access/middleware.mjs'
|
||||
import { serveIndex } from './serveindex.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')
|
||||
}
|
||||
const restrictAdmin = restrict(access.Manager)
|
||||
|
||||
export function serve(docRoot, pathname, options = {}) {
|
||||
options.root = docRoot
|
||||
|
||||
return (ctx, next) => {
|
||||
return async (ctx, next) => {
|
||||
let opts = defaults({}, options)
|
||||
if (ctx.request.method === 'OPTIONS') return
|
||||
|
||||
|
@ -96,16 +24,25 @@ export function serve(docRoot, pathname, options = {}) {
|
|||
|| filepath.endsWith('.js')
|
||||
|| filepath.endsWith('.css')
|
||||
|| filepath.endsWith('.svg')) {
|
||||
opts = defaults({ maxage: 2592000 * 1000 }, opts)
|
||||
if (filepath.indexOf('admin') === -1) {
|
||||
opts = defaults({ maxage: 2592000 * 1000 }, opts)
|
||||
}
|
||||
}
|
||||
|
||||
if (filepath === '/index.html') {
|
||||
return sendIndex(ctx, '/')
|
||||
return serveIndex(ctx, '/')
|
||||
}
|
||||
|
||||
if (filepath.indexOf('admin') >= 0
|
||||
&& (filepath.indexOf('js') >= 0
|
||||
|| filepath.indexOf('css') >= 0)) {
|
||||
await restrictAdmin(ctx)
|
||||
ctx.set('Cache-Control', 'no-store, no-cache, must-revalidate')
|
||||
}
|
||||
|
||||
return send(ctx, filepath, opts).catch((er) => {
|
||||
if (er.code === 'ENOENT' && er.status === 404) {
|
||||
return sendIndex(ctx)
|
||||
return serveIndex(ctx, filepath)
|
||||
// return send(ctx, '/index.html', options)
|
||||
}
|
||||
})
|
||||
|
|
149
api/serveindex.mjs
Normal file
149
api/serveindex.mjs
Normal file
|
@ -0,0 +1,149 @@
|
|||
import { readFileSync } from 'fs'
|
||||
import dot from 'dot'
|
||||
import striptags from 'striptags'
|
||||
|
||||
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)
|
||||
|
||||
function mapArticle(x) {
|
||||
return {
|
||||
id: x.id,
|
||||
created_at: x.created_at,
|
||||
path: x.path,
|
||||
description: x.description,
|
||||
name: x.name,
|
||||
media: x.media && ({
|
||||
large_url: x.media.large_url,
|
||||
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,
|
||||
parent: x.parent && ({
|
||||
id: x.parent.id,
|
||||
name: x.parent.name,
|
||||
path: x.parent.path,
|
||||
}),
|
||||
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,
|
||||
})),
|
||||
},
|
||||
}) || {},
|
||||
})) || [],
|
||||
}
|
||||
}
|
||||
|
||||
function mapPage(x) {
|
||||
return {
|
||||
id: x.id,
|
||||
created_at: x.created_at,
|
||||
path: x.path,
|
||||
description: x.description,
|
||||
name: x.name,
|
||||
media: x.media && ({
|
||||
large_url: x.media.large_url,
|
||||
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,
|
||||
children: x.children && x.children.map(f => ({
|
||||
id: f.id,
|
||||
path: f.path,
|
||||
name: f.name,
|
||||
})) || [],
|
||||
}
|
||||
}
|
||||
|
||||
export async function serveIndex(ctx, path) {
|
||||
let tree = null
|
||||
let data = null
|
||||
let links = null
|
||||
let image = '/assets/img/heart.jpg'
|
||||
let title = 'NFP Moe - Anime/Manga translation group'
|
||||
let description = 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.'
|
||||
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(Number(ctx.query.page || '1'))
|
||||
|
||||
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(mapArticle)
|
||||
} else if (path.startsWith('/article/') || path.startsWith('/page/')) {
|
||||
let id = path.split('/')[2]
|
||||
if (id) {
|
||||
let found
|
||||
if (path.startsWith('/article/')) {
|
||||
found = await Article.getSingle(id, ['media', 'parent', 'banner', 'files'])
|
||||
found = mapArticle(found.toJSON())
|
||||
data = found
|
||||
} else {
|
||||
found = await Page.getSingle(id, ['media', 'banner', 'children'])
|
||||
found = mapPage(found.toJSON())
|
||||
data = found
|
||||
}
|
||||
if (found.media) {
|
||||
image = found.media.large_url
|
||||
} else if (found.banner) {
|
||||
image = found.banner.large_url
|
||||
}
|
||||
if (found.description) {
|
||||
description = striptags(found.description)
|
||||
}
|
||||
if (found.parent) {
|
||||
title = found.name + ' - ' + found.parent.name + ' - NFP Moe'
|
||||
} else {
|
||||
title = found.name + ' - NFP Moe'
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
ctx.log.error(e)
|
||||
data = null
|
||||
links = null
|
||||
}
|
||||
ctx.body = bodyTemplate({
|
||||
v: config.get('CIRCLECI_VERSION'),
|
||||
tree: JSON.stringify(tree),
|
||||
data: JSON.stringify(data),
|
||||
links: JSON.stringify(links),
|
||||
image: image,
|
||||
title: title,
|
||||
description: description,
|
||||
})
|
||||
ctx.set('Content-Length', Buffer.byteLength(ctx.body))
|
||||
ctx.set('Cache-Control', 'max-age=0')
|
||||
ctx.set('Content-Type', 'text/html; charset=utf-8')
|
||||
}
|
13
app/admin.js
13
app/admin.js
|
@ -5,11 +5,8 @@ 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],
|
||||
]
|
||||
window.adminRoutes = {
|
||||
pages: [AdminPages, EditPage],
|
||||
articles: [AdminArticles, EditArticle],
|
||||
staff: [AdminStaffList, EditStaff],
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ const AdminArticles = {
|
|||
this.links = null
|
||||
this.lastpage = m.route.param('page') || '1'
|
||||
|
||||
document.title = 'Articles Page ' + this.lastpage + ' - Admin NFP Moe'
|
||||
|
||||
return pagination.fetchPage(Article.getAllArticlesPagination({
|
||||
per_page: 10,
|
||||
page: this.lastpage,
|
||||
|
|
|
@ -45,6 +45,9 @@ const EditArticle = {
|
|||
onupdate: function(vnode) {
|
||||
if (this.lastid !== m.route.param('id')) {
|
||||
this.fetchArticle(vnode)
|
||||
if (this.lastid === 'add') {
|
||||
m.redraw()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -63,7 +66,6 @@ const EditArticle = {
|
|||
files: [],
|
||||
}
|
||||
this.editedPath = false
|
||||
this.froala = null
|
||||
this.loadedFroala = Froala.loadedFroala
|
||||
|
||||
if (this.lastid !== 'add') {
|
||||
|
@ -71,14 +73,23 @@ const EditArticle = {
|
|||
.then(function(result) {
|
||||
vnode.state.editedPath = true
|
||||
vnode.state.article = result
|
||||
document.title = 'Editing: ' + result.name + ' - Admin NFP Moe'
|
||||
})
|
||||
.catch(function(err) {
|
||||
vnode.state.error = err.message
|
||||
})
|
||||
.then(function() {
|
||||
vnode.state.loading = false
|
||||
if (vnode.state.froala) {
|
||||
vnode.state.froala.html.set(vnode.state.article.description)
|
||||
}
|
||||
m.redraw()
|
||||
})
|
||||
} else {
|
||||
document.title = 'Create Article - Admin NFP Moe'
|
||||
if (vnode.state.froala) {
|
||||
vnode.state.froala.html.set(this.article.description)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -216,6 +227,7 @@ const EditArticle = {
|
|||
onclick: function() { vnode.state.error = '' },
|
||||
}, this.error),
|
||||
m(FileUpload, {
|
||||
height: 300,
|
||||
onupload: this.mediaUploaded.bind(this, 'banner'),
|
||||
onerror: function(e) { vnode.state.error = e },
|
||||
ondelete: this.mediaRemoved.bind(this, 'banner'),
|
||||
|
|
|
@ -43,6 +43,7 @@ const EditPage = {
|
|||
.then(function(result) {
|
||||
vnode.state.editedPath = true
|
||||
vnode.state.page = result
|
||||
document.title = 'Editing: ' + result.name + ' - Admin NFP Moe'
|
||||
})
|
||||
.catch(function(err) {
|
||||
vnode.state.error = err.message
|
||||
|
@ -51,6 +52,8 @@ const EditPage = {
|
|||
vnode.state.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
} else {
|
||||
document.title = 'Create Page - Admin NFP Moe'
|
||||
}
|
||||
|
||||
if (!this.loadedFroala) {
|
||||
|
|
|
@ -28,6 +28,7 @@ const EditStaff = {
|
|||
.then(function(result) {
|
||||
vnode.state.editedPath = true
|
||||
vnode.state.staff = result
|
||||
document.title = 'Editing: ' + result.fullname + ' - Admin NFP Moe'
|
||||
})
|
||||
.catch(function(err) {
|
||||
vnode.state.error = err.message
|
||||
|
@ -36,6 +37,8 @@ const EditStaff = {
|
|||
vnode.state.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
} else {
|
||||
document.title = 'Creating Staff Member - Admin NFP Moe'
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ const AdminPages = {
|
|||
this.pages = []
|
||||
this.removePage = null
|
||||
|
||||
document.title = 'Pages - Admin NFP Moe'
|
||||
|
||||
Page.getAllPages()
|
||||
.then(function(result) {
|
||||
vnode.state.pages = AdminPages.parseTree(result)
|
||||
|
|
|
@ -15,6 +15,8 @@ const AdminStaffList = {
|
|||
fetchStaffs: function(vnode) {
|
||||
this.loading = true
|
||||
|
||||
document.title = 'Staff members - Admin NFP Moe'
|
||||
|
||||
return Staff.getAllStaff()
|
||||
.then(function(result) {
|
||||
vnode.state.staff = result
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
const common = require('./common')
|
||||
|
||||
exports.uploadMedia = function(file) {
|
||||
exports.uploadMedia = function(file, height) {
|
||||
let formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
let extra = ''
|
||||
if (height) {
|
||||
extra = '?height=' + height
|
||||
}
|
||||
|
||||
return common.sendRequest({
|
||||
method: 'POST',
|
||||
url: '/api/media',
|
||||
url: '/api/media' + extra,
|
||||
body: formData,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,6 +14,6 @@ exports.getTree = function() {
|
|||
exports.getPage = function(id) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/pages/' + id + '?includes=media,banner,children,news,news.media',
|
||||
url: '/api/pages/' + id + '?includes=media,banner,children',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,13 +8,18 @@ const Article = {
|
|||
this.error = ''
|
||||
this.lastarticle = m.route.param('article') || '1'
|
||||
this.loadingnews = false
|
||||
this.fetchArticle(vnode)
|
||||
|
||||
if (window.__nfpdata) {
|
||||
this.path = m.route.param('id')
|
||||
this.article = window.__nfpdata
|
||||
window.__nfpdata = null
|
||||
} else {
|
||||
this.fetchArticle(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
fetchArticle: function(vnode) {
|
||||
this.path = m.route.param('id')
|
||||
this.news = []
|
||||
this.newslinks = null
|
||||
this.article = {
|
||||
id: 0,
|
||||
name: '',
|
||||
|
@ -29,6 +34,11 @@ const Article = {
|
|||
ApiArticle.getArticle(this.path)
|
||||
.then(function(result) {
|
||||
vnode.state.article = result
|
||||
if (result.parent) {
|
||||
document.title = result.name + ' - ' + result.parent.name + ' - NFP Moe'
|
||||
} else {
|
||||
document.title = result.name + ' - NFP Moe'
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
vnode.state.error = err.message
|
||||
|
|
|
@ -101,7 +101,15 @@ only screen and ( min-resolution: 2dppx) {
|
|||
@media (pointer:coarse) {
|
||||
footer .sitemap a.root,
|
||||
footer .sitemap a.child {
|
||||
// padding: 10px 10px;
|
||||
// display: inline-block;
|
||||
padding: 10px 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px){
|
||||
footer .sitemap a.root,
|
||||
footer .sitemap a.child {
|
||||
padding: 9px 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,15 @@ const Frontpage = {
|
|||
&& window.__nfplinks) {
|
||||
this.links = window.__nfplinks
|
||||
this.articles = window.__nfpdata
|
||||
this.lastpage = '1'
|
||||
this.lastpage = m.route.param('page') || '1'
|
||||
window.__nfpdata = null
|
||||
window.__nfplinks = null
|
||||
Frontpage.processFeatured(vnode, this.articles)
|
||||
|
||||
if (this.articles.length === 0) {
|
||||
m.route.set('/')
|
||||
} else {
|
||||
Frontpage.processFeatured(vnode, this.articles)
|
||||
}
|
||||
} else {
|
||||
this.fetchArticles(vnode)
|
||||
}
|
||||
|
@ -39,6 +44,12 @@ const Frontpage = {
|
|||
this.articles = []
|
||||
this.lastpage = m.route.param('page') || '1'
|
||||
|
||||
if (this.lastpage !== '1') {
|
||||
document.title = 'Page ' + this.lastpage + ' - NFP Moe - Anime/Manga translation group'
|
||||
} else {
|
||||
document.title = 'NFP Moe - Anime/Manga translation group'
|
||||
}
|
||||
|
||||
return Pagination.fetchPage(Article.getAllArticlesPagination({
|
||||
per_page: 10,
|
||||
page: this.lastpage,
|
||||
|
@ -59,6 +70,7 @@ const Frontpage = {
|
|||
},
|
||||
|
||||
processFeatured: function(vnode, data) {
|
||||
if (vnode.state.featured) return
|
||||
for (var i = data.length - 1; i >= 0; i--) {
|
||||
if (data[i].banner) {
|
||||
vnode.state.featured = data[i]
|
||||
|
|
|
@ -143,6 +143,16 @@ frontpage {
|
|||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
frontpage aside.sidebar a {
|
||||
padding: 9px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (pointer:coarse) {
|
||||
frontpage aside.sidebar a {
|
||||
padding: 9px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.darkmodeon {
|
||||
|
|
92
app/index.js
92
app/index.js
|
@ -1,51 +1,29 @@
|
|||
const m = require('mithril')
|
||||
window.m = m
|
||||
|
||||
m.route.prefix = ''
|
||||
|
||||
const Menu = require('./menu/menu')
|
||||
const Footer = require('./footer/footer')
|
||||
const Frontpage = require('./frontpage/frontpage')
|
||||
const Login = require('./login/login')
|
||||
const Logout = require('./login/logout')
|
||||
const Page = require('./pages/page')
|
||||
const Article = require('./article/article')
|
||||
const Authentication = require('./authentication')
|
||||
|
||||
const menuRoot = document.getElementById('nav')
|
||||
const mainRoot = document.getElementById('main')
|
||||
const footerRoot = document.getElementById('footer')
|
||||
|
||||
const allRoutes = {
|
||||
'/': Frontpage,
|
||||
'/login': Login,
|
||||
'/logout': Logout,
|
||||
'/page/:id': Page,
|
||||
'/article/:id': Article,
|
||||
}
|
||||
|
||||
m.route(mainRoot, '/', allRoutes)
|
||||
m.mount(menuRoot, Menu)
|
||||
m.mount(footerRoot, Footer)
|
||||
|
||||
m.route.prefix = ''
|
||||
window.adminRoutes = {}
|
||||
let loadingAdmin = false
|
||||
let loadedAdmin = false
|
||||
let loaded = 0
|
||||
let elements = []
|
||||
|
||||
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()
|
||||
m.route.set(m.route.get())
|
||||
}
|
||||
|
||||
const onError = function() {
|
||||
elements.forEach(function(x) { x.remove() })
|
||||
loadedAdmin = loadingAdmin = false
|
||||
loaded = 0
|
||||
m.route.set('/logout')
|
||||
}
|
||||
|
||||
const loadAdmin = function(user) {
|
||||
|
@ -59,17 +37,22 @@ const loadAdmin = function(user) {
|
|||
|
||||
loadingAdmin = true
|
||||
|
||||
let token = Authentication.getToken()
|
||||
let element = document.createElement('link')
|
||||
elements.push(element)
|
||||
element.setAttribute('rel', 'stylesheet')
|
||||
element.setAttribute('type', 'text/css')
|
||||
element.setAttribute('href', '/assets/admin.css')
|
||||
element.setAttribute('href', '/assets/admin.css?token=' + token)
|
||||
element.onload = onLoaded
|
||||
element.onerror = onError
|
||||
document.getElementsByTagName('head')[0].appendChild(element)
|
||||
|
||||
element = document.createElement('script')
|
||||
elements.push(element)
|
||||
element.setAttribute('type', 'text/javascript')
|
||||
element.setAttribute('src', '/assets/admin.js')
|
||||
element.setAttribute('src', '/assets/admin.js?token=' + token)
|
||||
element.onload = onLoaded
|
||||
element.onerror = onError
|
||||
document.body.appendChild(element)
|
||||
}
|
||||
|
||||
|
@ -77,3 +60,42 @@ Authentication.addEvent(loadAdmin)
|
|||
if (Authentication.currentUser) {
|
||||
loadAdmin(Authentication.currentUser)
|
||||
}
|
||||
|
||||
const Menu = require('./menu/menu')
|
||||
const Footer = require('./footer/footer')
|
||||
const Frontpage = require('./frontpage/frontpage')
|
||||
const Login = require('./login/login')
|
||||
const Logout = require('./login/logout')
|
||||
const Page = require('./pages/page')
|
||||
const Article = require('./article/article')
|
||||
|
||||
const menuRoot = document.getElementById('nav')
|
||||
const mainRoot = document.getElementById('main')
|
||||
const footerRoot = document.getElementById('footer')
|
||||
|
||||
const Loader = {
|
||||
view: function() { return m('div.loading-spinner') },
|
||||
}
|
||||
const AdminResolver = {
|
||||
onmatch: function(args, requestedPath) {
|
||||
if (window.adminRoutes[args.path]) {
|
||||
return window.adminRoutes[args.path][args.id && 1 || 0]
|
||||
}
|
||||
return Loader
|
||||
},
|
||||
render: function(vnode) { return vnode },
|
||||
}
|
||||
|
||||
const allRoutes = {
|
||||
'/': Frontpage,
|
||||
'/login': Login,
|
||||
'/logout': Logout,
|
||||
'/page/:id': Page,
|
||||
'/article/:id': Article,
|
||||
'/admin/:path': AdminResolver,
|
||||
'/admin/:path/:id': AdminResolver,
|
||||
}
|
||||
|
||||
m.route(mainRoot, '/', allRoutes)
|
||||
m.mount(menuRoot, Menu)
|
||||
m.mount(footerRoot, Footer)
|
||||
|
|
|
@ -6,12 +6,12 @@ const Logout = {
|
|||
Authentication.createGoogleScript()
|
||||
.then(function() {
|
||||
return new Promise(function (res) {
|
||||
gapi.load('auth2', res);
|
||||
gapi.load('auth2', res)
|
||||
})
|
||||
})
|
||||
.then(function() { return gapi.auth2.init() })
|
||||
.then(function() {
|
||||
let auth2 = gapi.auth2.getAuthInstance();
|
||||
let auth2 = gapi.auth2.getAuthInstance()
|
||||
return auth2.signOut()
|
||||
})
|
||||
.then(function() {
|
||||
|
|
|
@ -11,7 +11,17 @@ const Page = {
|
|||
this.error = ''
|
||||
this.lastpage = m.route.param('page') || '1'
|
||||
this.loadingnews = false
|
||||
this.fetchPage(vnode)
|
||||
|
||||
if (window.__nfpdata) {
|
||||
this.path = m.route.param('id')
|
||||
this.page = window.__nfpdata
|
||||
this.news = []
|
||||
this.newslinks = null
|
||||
window.__nfpdata = null
|
||||
vnode.state.fetchArticles(vnode)
|
||||
} else {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
fetchPage: function(vnode) {
|
||||
|
@ -30,6 +40,7 @@ const Page = {
|
|||
ApiPage.getPage(this.path)
|
||||
.then(function(result) {
|
||||
vnode.state.page = result
|
||||
document.title = result.name + ' - NFP Moe'
|
||||
})
|
||||
.catch(function(err) {
|
||||
vnode.state.error = err.message
|
||||
|
@ -71,11 +82,26 @@ const Page = {
|
|||
},
|
||||
|
||||
view: function(vnode) {
|
||||
var deviceWidth = window.innerWidth
|
||||
var bannerPath = ''
|
||||
|
||||
if (this.page && this.page.banner) {
|
||||
var pixelRatio = window.devicePixelRatio || 1
|
||||
if (deviceWidth < 400 && pixelRatio <= 1) {
|
||||
bannerPath = this.page.banner.small_url
|
||||
} else if ((deviceWidth < 800 && pixelRatio <= 1)
|
||||
|| (deviceWidth < 600 && pixelRatio > 1)) {
|
||||
bannerPath = this.page.banner.medium_url
|
||||
} else {
|
||||
bannerPath = this.page.banner.large_url
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
this.loading ?
|
||||
m('div.loading-spinner')
|
||||
: m('article.page', [
|
||||
this.page.banner ? m('.div.page-banner', { style: { 'background-image': 'url("' + this.page.banner.url + '")' } } ) : null,
|
||||
bannerPath ? m('.div.page-banner', { style: { 'background-image': 'url("' + bannerPath + '")' } } ) : null,
|
||||
m('header', m('h1', this.page.name)),
|
||||
m('.container', {
|
||||
class: this.page.children.length ? 'multi' : '',
|
||||
|
@ -90,7 +116,7 @@ const Page = {
|
|||
: null,
|
||||
this.page.description
|
||||
? m('.fr-view', [
|
||||
this.page.media ? m('img.page-cover', { src: this.page.media.url, alt: 'Cover image for ' + this.page.name } ) : null,
|
||||
this.page.media ? m('img.page-cover', { src: this.page.media.medium_url, alt: 'Cover image for ' + this.page.name } ) : null,
|
||||
m.trust(this.page.description),
|
||||
this.news.length && this.page.description
|
||||
? m('aside.news', [
|
||||
|
@ -107,7 +133,7 @@ const Page = {
|
|||
])
|
||||
: this.news.length
|
||||
? m('aside.news.single', [
|
||||
this.page.media ? m('img.page-cover', { src: this.page.media.url, alt: 'Cover image for ' + this.page.name } ) : null,
|
||||
this.page.media ? m('img.page-cover', { src: this.page.media.medium_url, alt: 'Cover image for ' + this.page.name } ) : null,
|
||||
m('h4', 'Latest posts under ' + this.page.name + ':'),
|
||||
this.loadingnews ? m('div.loading-spinner') : this.news.map(function(article) {
|
||||
return m(Newsentry, article)
|
||||
|
@ -118,7 +144,7 @@ const Page = {
|
|||
}),
|
||||
])
|
||||
: this.page.media
|
||||
? m('img.page-cover.single', { src: this.page.media.url, alt: 'Cover image for ' + this.page.name } )
|
||||
? m('img.page-cover.single', { src: this.page.media.medium_url, alt: 'Cover image for ' + this.page.name } )
|
||||
: null,
|
||||
]),
|
||||
Authentication.currentUser
|
||||
|
|
|
@ -6,7 +6,7 @@ const FileUpload = {
|
|||
vnode.state.updateError(vnode, '')
|
||||
vnode.state.loading = true
|
||||
|
||||
Media.uploadMedia(event.target.files[0])
|
||||
Media.uploadMedia(event.target.files[0], vnode.attrs.height || null)
|
||||
.then(function(res) {
|
||||
if (vnode.attrs.onupload) {
|
||||
vnode.attrs.onupload(res)
|
||||
|
|
|
@ -66,7 +66,8 @@
|
|||
"nconf": "^0.10.0",
|
||||
"parse-torrent": "^7.0.1",
|
||||
"pg": "^7.8.0",
|
||||
"sharp": "^0.22.1"
|
||||
"sharp": "^0.22.1",
|
||||
"striptags": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^16.2.3",
|
||||
|
|
BIN
public/assets/img/heart.jpg
Normal file
BIN
public/assets/img/heart.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
BIN
public/assets/img/heart.xcf
Normal file
BIN
public/assets/img/heart.xcf
Normal file
Binary file not shown.
|
@ -2,9 +2,20 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>NFP Moe</title>
|
||||
<title>{{=it.title}}</title>
|
||||
<base href="/">
|
||||
<meta name="description" content="{{=it.description}}">
|
||||
<meta name="twitter:card" value="summary">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta id="ogimage" property="og:image" content="{{=it.image}}" />
|
||||
<meta property="og:description" content="{{=it.description}}" />
|
||||
{{? it.image === '/assets/img/heart.jpg' }}
|
||||
<meta id="ogimagewidth" property="og:image:width" content="400" />
|
||||
<meta id="ogimageheight" property="og:image:height" content="500" />
|
||||
{{?? true }}
|
||||
{{? }}
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
|
||||
<link rel="Stylesheet" href="/assets/app.css?v={{=it.v}}" type="text/css" />
|
||||
<link rel="preconnect" href="https://cdn-nfp.global.ssl.fastly.net" />
|
||||
|
|
Loading…
Reference in a new issue