Big development, almost finished a complete refactor
This commit is contained in:
parent
296bf51c9a
commit
95e3737e91
46 changed files with 972 additions and 1486 deletions
|
@ -39,12 +39,12 @@ export default class ArticleRoutes {
|
|||
let res = await ctx.db.safeCallProc('article_auth_get_all', [
|
||||
ctx.state.auth_token,
|
||||
Math.max(ctx.query.get('page') || 1, 1),
|
||||
Math.min(ctx.query.get('per_page') || 10, 25)
|
||||
Math.min(ctx.query.get('per_page') || 20, 100)
|
||||
])
|
||||
|
||||
let out = {
|
||||
articles: parseArticles(res.results[0]),
|
||||
total_articles: res.results[0][0].total_articles,
|
||||
total_articles: res.results[1][0].total_articles,
|
||||
}
|
||||
|
||||
ctx.body = out
|
||||
|
|
|
@ -15,6 +15,14 @@ export default class Server {
|
|||
this.port = port
|
||||
this.core = core
|
||||
|
||||
this.flaskaOptions = {
|
||||
appendHeaders: {
|
||||
'Content-Security-Policy': `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`,
|
||||
},
|
||||
log: this.core.log,
|
||||
nonce: ['script-src'],
|
||||
nonceCacheLength: 50,
|
||||
}
|
||||
this.authenticate = authenticate
|
||||
this.formidable = FormidableHandler.bind(this, formidable)
|
||||
this.jsonHandler = JsonHandler
|
||||
|
@ -23,8 +31,12 @@ export default class Server {
|
|||
new ArticleRoutes(),
|
||||
new AuthenticationRoutes(),
|
||||
]
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() { }
|
||||
|
||||
getRouteInstance(type) {
|
||||
for (let route of this.routes) {
|
||||
if (route instanceof type) {
|
||||
|
@ -39,14 +51,7 @@ export default class Server {
|
|||
|
||||
runCreateServer() {
|
||||
// Create our server
|
||||
this.flaska = new Flaska({
|
||||
appendHeaders: {
|
||||
'Content-Security-Policy': `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`,
|
||||
},
|
||||
log: this.core.log,
|
||||
nonce: ['script-src'],
|
||||
nonceCacheLength: 50,
|
||||
}, this.http)
|
||||
this.flaska = new Flaska(this.flaskaOptions, this.http)
|
||||
|
||||
// Create our database pool
|
||||
let pool = this.runCreateDatabase()
|
||||
|
|
|
@ -4,6 +4,10 @@ import ServeHandler from '../base/serve.mjs'
|
|||
import PageRoutes from '../base/page/routes.mjs'
|
||||
|
||||
export default class Server extends Parent {
|
||||
init() {
|
||||
this.flaskaOptions.appendHeaders['Content-Security-Policy'] = `default-src 'self'; script-src 'self' talk.hyvor.com; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; iframe-src talk.hyvor.com` //; frame-ancestors 'none'`
|
||||
}
|
||||
|
||||
addCustomRoutes() {
|
||||
let page = this.getRouteInstance(PageRoutes)
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
require('./dtsel')
|
||||
const FileUpload = require('../widgets/fileupload')
|
||||
const Page = require('../api/page')
|
||||
const Fileinfo = require('../widgets/fileinfo')
|
||||
const common = require('../api/common')
|
||||
const FileUpload = require('./fileupload')
|
||||
const PageTree = require('../page_tree')
|
||||
const Fileinfo = require('../fileinfo')
|
||||
const api = require('../api')
|
||||
const Editor = require('./editor')
|
||||
|
||||
const EditArticle = {
|
||||
|
@ -16,7 +16,7 @@ const EditArticle = {
|
|||
staff: [],
|
||||
}
|
||||
this.pages = [{id: null, name: 'Frontpage'}]
|
||||
this.pages = this.pages.concat(Page.getFlatTree())
|
||||
this.pages = this.pages.concat(PageTree.getFlatTree())
|
||||
this.newBanner = null
|
||||
this.newMedia = null
|
||||
this.dateInstance = null
|
||||
|
@ -35,10 +35,11 @@ const EditArticle = {
|
|||
this.lastid = m.route.param('id')
|
||||
|
||||
return this.requestArticle(
|
||||
common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/auth/articles/' + (this.lastid === 'add' ? '0' : this.lastid),
|
||||
}))
|
||||
api.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/auth/articles/' + (this.lastid === 'add' ? '0' : this.lastid),
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
requestArticle: function(data) {
|
||||
|
@ -152,7 +153,7 @@ const EditArticle = {
|
|||
.then(body => {
|
||||
formData.append('content', JSON.stringify(body))
|
||||
|
||||
return common.sendRequest({
|
||||
return api.sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/api/auth/articles/' + (this.lastid === 'add' ? '0' : this.lastid),
|
||||
body: formData,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const FileUpload = require('../widgets/fileupload')
|
||||
const Page = require('../api/page.p')
|
||||
const FileUpload = require('./fileupload')
|
||||
const PageTree = require('../page_tree')
|
||||
|
||||
const common = require('../api/common')
|
||||
const api = require('../api')
|
||||
const Editor = require('./editor')
|
||||
|
||||
const EditPage = {
|
||||
|
@ -12,7 +12,7 @@ const EditPage = {
|
|||
page: null,
|
||||
}
|
||||
this.pages = [{id: null, name: 'Frontpage'}]
|
||||
this.pages = this.pages.concat(Page.getFlatTree())
|
||||
this.pages = this.pages.concat(PageTree.getFlatTree())
|
||||
|
||||
this.newBanner = null
|
||||
this.newMedia = null
|
||||
|
@ -31,10 +31,11 @@ const EditPage = {
|
|||
this.lastid = m.route.param('id')
|
||||
|
||||
return this.requestPage(
|
||||
common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/auth/pages/' + (this.lastid === 'add' ? '0' : this.lastid),
|
||||
}))
|
||||
api.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/auth/pages/' + (this.lastid === 'add' ? '0' : this.lastid),
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
requestPage: function(data) {
|
||||
|
@ -135,7 +136,7 @@ const EditPage = {
|
|||
.then(body => {
|
||||
formData.append('content', JSON.stringify(body))
|
||||
|
||||
return common.sendRequest({
|
||||
return api.sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/api/auth/pages/' + (this.lastid === 'add' ? '0' : this.lastid),
|
||||
body: formData,
|
||||
|
@ -148,9 +149,9 @@ const EditPage = {
|
|||
this.lastid = data.page.id.toString()
|
||||
m.route.set('/admin/pages/' + data.page.id)
|
||||
}
|
||||
return Page.refreshTree().then(() => {
|
||||
return PageTree.refreshTree().then(() => {
|
||||
this.pages = [{id: null, name: 'Frontpage'}]
|
||||
this.pages = this.pages.concat(Page.getFlatTree())
|
||||
this.pages = this.pages.concat(PageTree.getFlatTree())
|
||||
|
||||
return data
|
||||
})
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const Staff = require('../api/staff')
|
||||
|
||||
const EditStaff = {
|
||||
/*
|
||||
oninit: function(vnode) {
|
||||
this.fetchStaff(vnode)
|
||||
},
|
||||
|
@ -146,7 +145,7 @@ const EditStaff = {
|
|||
]),
|
||||
])
|
||||
)
|
||||
},
|
||||
},*/
|
||||
}
|
||||
|
||||
module.exports = EditStaff
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
const Article = require('../api/article')
|
||||
const pagination = require('../api/pagination')
|
||||
const Dialogue = require('../widgets/dialogue')
|
||||
const Pages = require('../widgets/pages')
|
||||
const common = require('../api/common')
|
||||
const Dialogue = require('./dialogue')
|
||||
const api = require('../api')
|
||||
const Paginator = require('../paginator')
|
||||
|
||||
const ItemsPerPage = 20
|
||||
|
||||
const AdminArticles = {
|
||||
oninit: function(vnode) {
|
||||
|
@ -47,7 +47,7 @@ const AdminArticles = {
|
|||
this.loading = true
|
||||
}
|
||||
|
||||
return common.sendRequest({
|
||||
return api.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/auth/articles?page=' + (this.lastpage || 1),
|
||||
})
|
||||
|
@ -100,8 +100,8 @@ const AdminArticles = {
|
|||
: ''
|
||||
}, [
|
||||
m('td', m(m.route.Link, { href: '/admin/articles/' + article.id }, article.name)),
|
||||
m('td', m(m.route.Link, { href: '/article/' + article.path }, 'View')),
|
||||
m('td', m(m.route.Link, { href: article.page_path }, article.page_name)),
|
||||
m('td', m(m.route.Link, { href: '/article/' + article.path }, '/article/' + article.path)),
|
||||
m('td.right', article.publish_at.replace('T', ' ').split('.')[0]),
|
||||
m('td.right', article.admin_name),
|
||||
m('td.right', m('button', { onclick: function() { vnode.state.removeArticle = article } }, 'Remove')),
|
||||
|
@ -111,13 +111,13 @@ const AdminArticles = {
|
|||
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m('div.admin-wrapper', [
|
||||
m('div.admin-actions', [
|
||||
m('div.wrapper.admin', [
|
||||
m('div.inside', [
|
||||
m('h2', 'All articles'),
|
||||
m('div.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() { vnode.state.error = '' },
|
||||
|
@ -128,8 +128,8 @@ const AdminArticles = {
|
|||
m('thead',
|
||||
m('tr', [
|
||||
m('th', 'Title'),
|
||||
m('th', 'Page'),
|
||||
m('th', 'Path'),
|
||||
m('th', 'Page'),
|
||||
m('th.right', 'Publish'),
|
||||
m('th.right', 'By'),
|
||||
m('th.right', 'Actions'),
|
||||
|
@ -138,10 +138,12 @@ const AdminArticles = {
|
|||
m('tbody', this.data.articles.map((article) => this.drawArticle(vnode, article))),
|
||||
],
|
||||
),
|
||||
/*m(Pages, {
|
||||
m(Paginator, {
|
||||
base: '/admin/articles',
|
||||
links: this.links,
|
||||
}),*/
|
||||
page: this.currentPage,
|
||||
perPage: ItemsPerPage,
|
||||
total: this.data.total_articles,
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
m(Dialogue, {
|
|
@ -1,6 +1,6 @@
|
|||
const Page = require('../api/page.p')
|
||||
const Dialogue = require('../widgets/dialogue')
|
||||
const common = require('../api/common')
|
||||
const PageTree = require('../page_tree')
|
||||
const Dialogue = require('./dialogue')
|
||||
const api = require('../api')
|
||||
|
||||
const AdminPages = {
|
||||
oninit: function(vnode) {
|
||||
|
@ -16,7 +16,7 @@ const AdminPages = {
|
|||
this.loading = true
|
||||
this.error = ''
|
||||
|
||||
return common.sendRequest({
|
||||
return api.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/auth/pages',
|
||||
})
|
||||
|
@ -37,11 +37,11 @@ const AdminPages = {
|
|||
this.loading = true
|
||||
m.redraw()
|
||||
|
||||
return common.sendRequest({
|
||||
return api.sendRequest({
|
||||
method: 'DELETE',
|
||||
url: '/api/auth/pages/' + removingPage.id,
|
||||
})
|
||||
.then(() => Page.refreshTree())
|
||||
.then(() => PageTree.refreshTree())
|
||||
.then(
|
||||
() => this.fetchPages(vnode),
|
||||
(err) => {
|
||||
|
@ -68,33 +68,37 @@ const AdminPages = {
|
|||
|
||||
view: function(vnode) {
|
||||
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: () => { this.fetchPages(vnode) },
|
||||
}, 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(AdminPages.drawPage.bind(this, vnode))),
|
||||
]),
|
||||
m('div.wrapper.admin', [
|
||||
m('div.inside', [
|
||||
m('h2', 'All pages'),
|
||||
m('div.actions', [
|
||||
m('span', 'Actions:'),
|
||||
m(m.route.Link, { href: '/admin/pages/add' }, 'Create new page'),
|
||||
]),
|
||||
])
|
||||
),
|
||||
m('div.error', {
|
||||
hidden: !this.error,
|
||||
onclick: function() { vnode.state.error = '' },
|
||||
}, this.error),
|
||||
this.loading
|
||||
? m('div.loading-spinner.full')
|
||||
: 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(AdminPages.drawPage.bind(this, vnode))),
|
||||
],
|
||||
),
|
||||
/*m(Pages, {
|
||||
base: '/admin/articles',
|
||||
links: this.links,
|
||||
}),*/
|
||||
]),
|
||||
]),
|
||||
m(Dialogue, {
|
||||
hidden: vnode.state.removePage === null,
|
||||
title: 'Delete ' + (vnode.state.removePage ? vnode.state.removePage.name : ''),
|
|
@ -1,8 +1,8 @@
|
|||
const Staff = require('../api/staff')
|
||||
const Dialogue = require('../widgets/dialogue')
|
||||
const Pages = require('../widgets/pages')
|
||||
const Dialogue = require('./dialogue')
|
||||
const Pages = require('../paginator')
|
||||
|
||||
const AdminStaffList = {
|
||||
/*
|
||||
oninit: function(vnode) {
|
||||
this.error = ''
|
||||
this.lastpage = m.route.param('page') || '1'
|
||||
|
@ -104,7 +104,7 @@ const AdminStaffList = {
|
|||
onno: function() { vnode.state.removeStaff = null },
|
||||
}),
|
||||
]
|
||||
},
|
||||
},*/
|
||||
}
|
||||
|
||||
module.exports = AdminStaffList
|
||||
|
|
83
nfp_moe/app/admin_loader.js
Normal file
83
nfp_moe/app/admin_loader.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
const Authentication = require('./authentication')
|
||||
|
||||
window.adminRoutes = {}
|
||||
|
||||
let loadingAdmin = false
|
||||
let loadedAdmin = false
|
||||
let loaded = 0
|
||||
let elements = []
|
||||
|
||||
const onLoaded = function() {
|
||||
loaded++
|
||||
if (loaded < 2) return
|
||||
|
||||
Authentication.setAdmin(Authentication.currentUser && Authentication.currentUser.rank >= 10)
|
||||
loadedAdmin = true
|
||||
m.route.set(m.route.get())
|
||||
}
|
||||
|
||||
const onError = function(a, b, c) {
|
||||
Authentication.clearToken()
|
||||
elements.forEach(function(x) { x.remove() })
|
||||
loadedAdmin = loadingAdmin = false
|
||||
loaded = 0
|
||||
m.route.set('/')
|
||||
}
|
||||
|
||||
const loadAdmin = function(user) {
|
||||
if (loadingAdmin) {
|
||||
if (loadedAdmin) {
|
||||
Authentication.setAdmin(user && user.rank >= 10)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!user || user.rank < 10) return
|
||||
|
||||
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?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?token=' + token)
|
||||
element.onload = onLoaded
|
||||
element.onerror = onError
|
||||
document.body.appendChild(element)
|
||||
|
||||
element = document.createElement('script')
|
||||
elements.push(element)
|
||||
element.setAttribute('type', 'text/javascript')
|
||||
element.setAttribute('src', '/assets/editor.js')
|
||||
element.onload = onLoaded
|
||||
element.onerror = onError
|
||||
document.body.appendChild(element)
|
||||
}
|
||||
|
||||
Authentication.addEvent(loadAdmin)
|
||||
if (Authentication.currentUser) {
|
||||
loadAdmin(Authentication.currentUser)
|
||||
}
|
||||
|
||||
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 },
|
||||
}
|
||||
|
||||
module.exports = AdminResolver
|
|
@ -1,4 +1,4 @@
|
|||
const Authentication = require('../authentication')
|
||||
const Authentication = require('./authentication')
|
||||
|
||||
exports.sendRequest = function(options, isPagination) {
|
||||
let token = Authentication.getToken()
|
|
@ -1,64 +0,0 @@
|
|||
const common = require('./common')
|
||||
|
||||
exports.createArticle = function(body) {
|
||||
return common.sendRequest({
|
||||
method: 'POST',
|
||||
url: '/api/articles',
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
|
||||
exports.updateArticle = function(id, body) {
|
||||
return common.sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/api/articles/' + id,
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
|
||||
exports.getAllArticles = function() {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/articles?includes=parent',
|
||||
})
|
||||
}
|
||||
|
||||
exports.getAllArticlesPagination = function(options) {
|
||||
let extra = ''
|
||||
|
||||
if (options.sort) {
|
||||
extra += '&sort=' + options.sort
|
||||
}
|
||||
if (options.per_page) {
|
||||
extra += '&perPage=' + options.per_page
|
||||
}
|
||||
if (options.page) {
|
||||
extra += '&page=' + options.page
|
||||
}
|
||||
if (options.includes) {
|
||||
extra += '&includes=' + options.includes.join(',')
|
||||
}
|
||||
|
||||
return '/api/articles?' + extra
|
||||
}
|
||||
|
||||
exports.getAllPageArticles = function(pageId, includes) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/pages/' + pageId + '/articles?includes=' + includes.join(','),
|
||||
})
|
||||
}
|
||||
|
||||
exports.getArticle = function(id) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/articles/' + id + '?includes=media,parent,banner,files',
|
||||
})
|
||||
}
|
||||
|
||||
exports.removeArticle = function(article, id) {
|
||||
return common.sendRequest({
|
||||
method: 'DELETE',
|
||||
url: '/api/articles/' + id,
|
||||
})
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
const common = require('./common')
|
||||
|
||||
exports.getArticle = function(id) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/articles/' + id,
|
||||
})
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
const common = require('./common')
|
||||
|
||||
exports.uploadFile = function(articleId, file) {
|
||||
let formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
return common.sendRequest({
|
||||
method: 'POST',
|
||||
url: '/api/articles/' + articleId + '/file',
|
||||
body: formData,
|
||||
})
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
const common = require('./common')
|
||||
|
||||
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' + extra,
|
||||
body: formData,
|
||||
})
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
const common = require('./common')
|
||||
|
||||
const Tree = window.__nfptree && window.__nfptree.tree || []
|
||||
|
||||
exports.Tree = Tree
|
||||
|
||||
exports.createPage = function(body) {
|
||||
return common.sendRequest({
|
||||
method: 'POST',
|
||||
url: '/api/pages',
|
||||
body: body,
|
||||
}).then(function(res) {
|
||||
res.children = []
|
||||
if (!res.parent_id) {
|
||||
Tree.push(res)
|
||||
} else {
|
||||
for (let i = 0; i < Tree.length; i++) {
|
||||
if (Tree[i].id === res.parent_id) {
|
||||
Tree[i].children.push(res)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
function processPageBranch(arr, branches, prefix) {
|
||||
branches.forEach((page) => {
|
||||
arr.push({ id: page.id, name: prefix + page.name })
|
||||
if (page.children && page.children.length) {
|
||||
processPageBranch(arr, page.children, page.name + ' -> ')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.getFlatTree = function() {
|
||||
let arr = []
|
||||
processPageBranch(arr, Tree, '')
|
||||
return arr
|
||||
}
|
||||
|
||||
exports.getTree = function() {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/pages?tree=true&includes=children&fields=id,name,path,children(id,name,path)',
|
||||
})
|
||||
}
|
||||
|
||||
exports.updatePage = function(id, body) {
|
||||
return common.sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/api/pages/' + id,
|
||||
body: body,
|
||||
}).then(function(res) {
|
||||
for (let i = 0; i < Tree.length; i++) {
|
||||
if (Tree[i].id === res.id) {
|
||||
res.children = Tree[i].children
|
||||
Tree[i] = res
|
||||
break
|
||||
} else if (Tree[i].id === res.parent_id) {
|
||||
for (let x = 0; x < Tree[i].children.length; x++) {
|
||||
if (Tree[i].children[x].id === res.id) {
|
||||
res.children = Tree[i].children[x].children
|
||||
Tree[i].children[x] = res
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!res.children) {
|
||||
res.children = []
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
exports.getAllPages = function() {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/pages',
|
||||
})
|
||||
}
|
||||
|
||||
exports.getPage = function(id) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/pages/' + id + '?includes=media,banner',
|
||||
})
|
||||
}
|
||||
|
||||
exports.removePage = function(page, id) {
|
||||
return common.sendRequest({
|
||||
method: 'DELETE',
|
||||
url: '/api/pages/' + id,
|
||||
}).then(function() {
|
||||
for (let i = 0; i < Tree.length; i++) {
|
||||
if (Tree[i].id === page.id) {
|
||||
Tree.splice(i, 1)
|
||||
break
|
||||
} else if (Tree[i].id === page.parent_id) {
|
||||
for (let x = 0; x < Tree[i].children.length; x++) {
|
||||
if (Tree[i].children[x].id === page.id) {
|
||||
Tree[i].children.splice(x, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
const common = require('./common')
|
||||
|
||||
function hasRel(x) {
|
||||
return x && x.rel;
|
||||
}
|
||||
|
||||
function intoRels (acc, x) {
|
||||
function splitRel (rel) {
|
||||
acc[rel] = xtend(x, { rel: rel });
|
||||
}
|
||||
|
||||
x.rel.split(/\s+/).forEach(splitRel);
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
function createObjects (acc, p) {
|
||||
// rel="next" => 1: rel 2: next
|
||||
var m = p.match(/\s*(.+)\s*=\s*"?([^"]+)"?/)
|
||||
if (m) acc[m[1]] = m[2];
|
||||
return acc;
|
||||
}
|
||||
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
function extend() {
|
||||
var target = {}
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var source = arguments[i]
|
||||
|
||||
for (var key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
function parseLink(link) {
|
||||
try {
|
||||
var m = link.match(/<?([^>]*)>(.*)/)
|
||||
, linkUrl = m[1]
|
||||
, parts = m[2].split(';')
|
||||
, qry = new URL(linkUrl).searchParams;
|
||||
|
||||
parts.shift();
|
||||
|
||||
var info = parts
|
||||
.reduce(createObjects, {});
|
||||
|
||||
info = extend(qry, info);
|
||||
info.url = linkUrl;
|
||||
return info;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parse(linkHeader) {
|
||||
return linkHeader.split(/,\s*</)
|
||||
.map(parseLink)
|
||||
.filter(hasRel)
|
||||
.reduce(intoRels, {});
|
||||
}
|
||||
|
||||
exports.fetchPage = function(url) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
}, true)
|
||||
.then(function(result) {
|
||||
return {
|
||||
data: result.data,
|
||||
links: parse(result.headers.link || ''),
|
||||
total: Number(result.headers.pagination_total || '0'),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
const common = require('./common')
|
||||
|
||||
exports.createStaff = function(body) {
|
||||
return common.sendRequest({
|
||||
method: 'POST',
|
||||
url: '/api/staff',
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
|
||||
exports.updateStaff = function(id, body) {
|
||||
return common.sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/api/staff/' + id,
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
|
||||
exports.getAllStaff = function() {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/staff',
|
||||
})
|
||||
}
|
||||
|
||||
exports.getStaff = function(id) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/staff/' + id,
|
||||
})
|
||||
}
|
||||
|
||||
exports.removeStaff = function(id) {
|
||||
return common.sendRequest({
|
||||
method: 'DELETE',
|
||||
url: '/api/staff/' + id,
|
||||
})
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
const Fileinfo = require('./fileinfo')
|
||||
const EditorBlock = require('./editorblock')
|
||||
|
||||
const Newsitem = {
|
||||
const Article = {
|
||||
oninit: function(vnode) {
|
||||
this.lastId = null
|
||||
this.onbeforeupdate(vnode)
|
||||
|
@ -22,9 +22,14 @@ const Newsitem = {
|
|||
+ article.media_alt_prefix + '_medium.avif' + ' 1300w, '
|
||||
+ article.media_alt_prefix + '_large.avif 1920w'
|
||||
|
||||
this.pictureCover = '(max-width: 639px) calc(100vw - 40px), '
|
||||
if (vnode.attrs.full) {
|
||||
this.pictureCover = '(max-width: 1280) calc(100vw - 2rem), '
|
||||
+ '1248px'
|
||||
} else {
|
||||
this.pictureCover = '(max-width: 639px) calc(100vw - 40px), '
|
||||
+ '(max-width: 1000px) 300px, '
|
||||
+ '400px'
|
||||
}
|
||||
} else {
|
||||
this.pictureFallback = null
|
||||
this.pictureJpeg = null
|
||||
|
@ -36,17 +41,21 @@ const Newsitem = {
|
|||
|
||||
view: function(vnode) {
|
||||
let article = vnode.attrs.article
|
||||
let files = vnode.attrs.files || article.files || []
|
||||
|
||||
return m('newsitem', [
|
||||
return m('article', {
|
||||
class: vnode.attrs.full ? 'fullsize' : '',
|
||||
}, [
|
||||
m(m.route.Link,
|
||||
{ href: '/article/' + article.path, class: 'title' },
|
||||
m('h3', [article.name])
|
||||
m('h2', article.name)
|
||||
),
|
||||
m('div.newsitemcontent', [
|
||||
m('div.row', [
|
||||
this.pictureFallback
|
||||
? m(m.route.Link, {
|
||||
? m(vnode.attrs.full ? 'a' : m.route.Link, {
|
||||
class: 'cover',
|
||||
href: '/article/' + article.path,
|
||||
target: vnode.attrs.full ? '_blank' : '',
|
||||
href: vnode.attrs.full ? article.media_path : '/article/' + article.path,
|
||||
},
|
||||
m('picture', [
|
||||
m('source', {
|
||||
|
@ -63,19 +72,18 @@ const Newsitem = {
|
|||
])
|
||||
)
|
||||
: null,
|
||||
m('div.entrycontent', [
|
||||
article.content.blocks.map(block => {
|
||||
return m(EditorBlock, { block: block })
|
||||
m('div', [
|
||||
m('div.description',
|
||||
article.content.blocks.map(block => {
|
||||
return m(EditorBlock, { block: block })
|
||||
}),
|
||||
),
|
||||
files.map(function(file) {
|
||||
return m(Fileinfo, { file: file, trim: true })
|
||||
}),
|
||||
(article.files && article.files.length
|
||||
? article.files.map(function(file) {
|
||||
return m(Fileinfo, { file: file, trim: true })
|
||||
})
|
||||
: null),
|
||||
m('span.entrymeta', [
|
||||
m('p.meta', [
|
||||
'Posted ',
|
||||
(article.page_path ? 'in' : ''),
|
||||
(article.page_path ? m(m.route.Link, { href: '/page/' + article.page_path }, article.page_name) : null),
|
||||
(article.page_path ? ['in ', m(m.route.Link, { href: '/page/' + article.page_path }, article.page_name), ' '] : ''),
|
||||
'at ' + (article.publish_at.replace('T', ' ').split('.')[0]).substr(0, 16),
|
||||
' by ' + (article.admin_name || 'Admin'),
|
||||
]),
|
||||
|
@ -85,4 +93,4 @@ const Newsitem = {
|
|||
},
|
||||
}
|
||||
|
||||
module.exports = Newsitem
|
||||
module.exports = Article
|
|
@ -1,199 +0,0 @@
|
|||
const m = require('mithril')
|
||||
const ApiArticle = require('../api/article.p')
|
||||
const Authentication = require('../authentication')
|
||||
const Fileinfo = require('../widgets/fileinfo')
|
||||
const EditorBlock = require('../widgets/editorblock')
|
||||
|
||||
const Article = {
|
||||
oninit: function(vnode) {
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
this.showLoading = null
|
||||
this.data = {
|
||||
article: null,
|
||||
files: [],
|
||||
}
|
||||
this.showcomments = false
|
||||
|
||||
if (window.__nfpdata) {
|
||||
this.path = m.route.param('id')
|
||||
this.data.article = window.__nfpdata
|
||||
window.__nfpdata = null
|
||||
} else {
|
||||
this.fetchArticle(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
onbeforeupdate: function(vnode) {
|
||||
if (this.path !== m.route.param('id')) {
|
||||
this.fetchArticle(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
fetchArticle: function(vnode) {
|
||||
this.error = ''
|
||||
this.path = m.route.param('id')
|
||||
this.showcomments = false
|
||||
|
||||
if (this.showLoading) {
|
||||
clearTimeout(this.showLoading)
|
||||
}
|
||||
|
||||
if (this.data.article) {
|
||||
this.showLoading = setTimeout(() => {
|
||||
this.showLoading = null
|
||||
this.loading = true
|
||||
m.redraw()
|
||||
}, 150)
|
||||
} else {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
ApiArticle.getArticle(this.path)
|
||||
.then((result) => {
|
||||
this.data = result
|
||||
|
||||
if (this.data.article.media_alt_prefix) {
|
||||
this.data.article.pictureFallback = this.data.article.media_alt_prefix + '_small.jpg'
|
||||
this.data.article.pictureJpeg = this.data.article.media_alt_prefix + '_small.jpg' + ' 720w, '
|
||||
+ this.data.article.media_alt_prefix + '_medium.jpg' + ' 1300w, '
|
||||
+ this.data.article.media_alt_prefix + '_large.jpg 1920w'
|
||||
this.data.article.pictureAvif = this.data.article.media_alt_prefix + '_small.avif' + ' 720w, '
|
||||
+ this.data.article.media_alt_prefix + '_medium.avif' + ' 1300w, '
|
||||
+ this.data.article.media_alt_prefix + '_large.avif 1920w'
|
||||
|
||||
this.data.article.pictureCover = '(max-width: 840px) calc(100vw - 82px), '
|
||||
+ '758px'
|
||||
} else {
|
||||
this.data.article.pictureFallback = null
|
||||
this.data.article.pictureJpeg = null
|
||||
this.data.article.pictureAvif = null
|
||||
this.data.article.pictureCover = null
|
||||
}
|
||||
|
||||
if (!this.data.article) {
|
||||
this.error = 'Article not found'
|
||||
}
|
||||
}, (err) => {
|
||||
this.error = err.message
|
||||
})
|
||||
.then(() => {
|
||||
clearTimeout(this.showLoading)
|
||||
this.showLoading = null
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
let article = this.data.article
|
||||
return (
|
||||
this.loading ?
|
||||
m('article.article', m('div.loading-spinner'))
|
||||
: this.error
|
||||
? m('div.error-wrapper', m('div.error', {
|
||||
onclick: function() {
|
||||
vnode.state.error = ''
|
||||
vnode.state.fetchArticle(vnode)
|
||||
},
|
||||
}, 'Article error: ' + this.error))
|
||||
: m('article.article', [
|
||||
article.page_path
|
||||
? m('div.goback', ['« ', m(m.route.Link, { href: '/page/' + article.page_path }, article.page_name)])
|
||||
: null,
|
||||
m('header', m('h1', article.name)),
|
||||
m('.fr-view', [
|
||||
article.pictureFallback
|
||||
? m('a.cover', {
|
||||
rel: 'noopener',
|
||||
href: article.media_path,
|
||||
}, [
|
||||
m('picture', [
|
||||
m('source', {
|
||||
srcset: article.pictureAvif,
|
||||
sizes: article.pictureCover,
|
||||
type: 'image/avif',
|
||||
}),
|
||||
m('img', {
|
||||
srcset: article.pictureJpeg,
|
||||
sizes: article.pictureCover,
|
||||
alt: 'Image for news item ' + article.name,
|
||||
src: article.pictureFallback,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
: null,
|
||||
article.content.blocks.map(block => {
|
||||
return m(EditorBlock, { block: block })
|
||||
}),
|
||||
this.data.files.map(function(file) {
|
||||
return m(Fileinfo, { file: file })
|
||||
}),
|
||||
m('div.entrymeta', [
|
||||
'Posted ',
|
||||
article.page_path
|
||||
? [
|
||||
'in',
|
||||
m(m.route.Link, { href: '/page/' + article.page_path }, article.page_name)
|
||||
]
|
||||
: '',
|
||||
'at ' + (article.publish_at.replace('T', ' ').split('.')[0]).substr(0, 16),
|
||||
' by ' + (article.admin_name || 'Admin'),
|
||||
]),
|
||||
]),
|
||||
Authentication.currentUser
|
||||
? m('div.admin-actions', [
|
||||
m('span', 'Admin controls:'),
|
||||
m(m.route.Link, { href: '/admin/articles/' + article.path }, 'Edit article'),
|
||||
])
|
||||
: null,
|
||||
this.showcomments
|
||||
? m('div.commentcontainer', [
|
||||
m('div#disqus_thread', { oncreate: function() {
|
||||
let fullhost = window.location.protocol + '//' + window.location.host
|
||||
/*eslint-disable */
|
||||
window.disqus_config = function () {
|
||||
this.page.url = fullhost + '/article/' + vnode.state.article.path
|
||||
this.page.identifier = 'article-' + vnode.state.article.id
|
||||
};
|
||||
(function() { // DON'T EDIT BELOW THIS LINE
|
||||
var d = document, s = d.createElement('script');
|
||||
s.src = 'https://nfp-moe.disqus.com/embed.js';
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
(d.head || d.body).appendChild(s);
|
||||
})()
|
||||
/*eslint-enable */
|
||||
}}, m('div.loading-spinner')),
|
||||
])
|
||||
: m('button.opencomments', {
|
||||
onclick: function() { vnode.state.showcomments = true },
|
||||
}, 'Open comment discussion'),
|
||||
])
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Article
|
||||
|
||||
/*
|
||||
<div id="disqus_thread"></div>
|
||||
<script>
|
||||
|
||||
/**
|
||||
* RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
|
||||
* LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
|
||||
/*
|
||||
var disqus_config = function () {
|
||||
this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable
|
||||
this.page.identifier = PAGE_IDENTIFIER; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
|
||||
};
|
||||
/
|
||||
(function() { // DON'T EDIT BELOW THIS LINE
|
||||
var d = document, s = d.createElement('script');
|
||||
s.src = 'https://nfp-moe.disqus.com/embed.js';
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
|
||||
*/
|
|
@ -1,6 +1,6 @@
|
|||
const Fileinfo = require('./fileinfo')
|
||||
|
||||
const Newsentry = {
|
||||
const Articleslim = {
|
||||
oninit: function(vnode) {
|
||||
this.lastId = null
|
||||
this.onbeforeupdate(vnode)
|
||||
|
@ -8,10 +8,10 @@ const Newsentry = {
|
|||
|
||||
strip: function(html) {
|
||||
var doc = new DOMParser().parseFromString(html, 'text/html')
|
||||
var out = doc.body.textContent || ''
|
||||
var out = (doc.body.textContent || '').replace(/([\.!?])/g, '$1 ')
|
||||
var splitted = out.split('.')
|
||||
if (splitted.length > 2) {
|
||||
return splitted.slice(0, 2).join('.') + '...'
|
||||
return splitted.slice(0, 2).join('.') + '...'
|
||||
}
|
||||
return out
|
||||
},
|
||||
|
@ -23,13 +23,15 @@ const Newsentry = {
|
|||
this.lastId = article.id
|
||||
this.description = null
|
||||
|
||||
for (let i = 0; i < article.content.blocks.length; i++) {
|
||||
if (article.content.blocks[i].type === 'paragraph') {
|
||||
this.description = article.content.blocks[i].data.text
|
||||
break
|
||||
} else if (article.content.blocks[i].type === 'htmlraw') {
|
||||
this.description = this.strip(article.content.blocks[i].data.html)
|
||||
break
|
||||
if (article.content) {
|
||||
for (let i = 0; i < article.content.blocks.length; i++) {
|
||||
if (article.content.blocks[i].type === 'paragraph') {
|
||||
this.description = article.content.blocks[i].data.text
|
||||
break
|
||||
} else if (article.content.blocks[i].type === 'htmlraw') {
|
||||
this.description = this.strip(article.content.blocks[i].data.html)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +58,7 @@ const Newsentry = {
|
|||
view: function(vnode) {
|
||||
let article = vnode.attrs.article
|
||||
|
||||
return m('newsentry', [
|
||||
return m('articleslim', [
|
||||
this.pictureFallback
|
||||
? m(m.route.Link, {
|
||||
class: 'cover',
|
||||
|
@ -77,23 +79,21 @@ const Newsentry = {
|
|||
])
|
||||
)
|
||||
: m('a.cover.nobg'),
|
||||
m('div.entrycontent', [
|
||||
m('div.title', [
|
||||
m(m.route.Link,
|
||||
{ href: '/article/' + article.path },
|
||||
m('h3', [article.name])
|
||||
),
|
||||
]),
|
||||
m('div', [
|
||||
m(m.route.Link,
|
||||
{ class: 'title', href: '/article/' + article.path },
|
||||
article.name
|
||||
),
|
||||
(article.files && article.files.length
|
||||
? article.files.map(function(file) {
|
||||
return m(Fileinfo, { file: file, slim: true })
|
||||
})
|
||||
: this.description
|
||||
? m('span.entrydescription', this.description)
|
||||
? m('p.description', this.description)
|
||||
: null),
|
||||
]),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Newsentry
|
||||
module.exports = Articleslim
|
|
@ -7,6 +7,7 @@ const Authentication = {
|
|||
authListeners: [],
|
||||
|
||||
updateToken: function(token) {
|
||||
console.log('updateToken', token)
|
||||
if (!token) return Authentication.clearToken()
|
||||
localStorage.setItem(storageName, token)
|
||||
Authentication.currentUser = JSON.parse(atob(token.split('.')[1]))
|
||||
|
@ -17,6 +18,8 @@ const Authentication = {
|
|||
},
|
||||
|
||||
clearToken: function() {
|
||||
var err = new Error()
|
||||
console.log('clearing', err.stack)
|
||||
Authentication.currentUser = null
|
||||
localStorage.removeItem(storageName)
|
||||
Authentication.isAdmin = false
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
const storageName = 'darkmode'
|
||||
|
||||
const Darkmode = {
|
||||
darkIsOn: false,
|
||||
|
||||
setDarkMode: function(setOn) {
|
||||
if (setOn) {
|
||||
localStorage.setItem(storageName, true)
|
||||
document.body.className = 'darkmodeon' + ' ' + (window.supportsavif ? 'avifsupport' : 'jpegonly')
|
||||
Darkmode.darkIsOn = true
|
||||
} else {
|
||||
localStorage.removeItem(storageName)
|
||||
document.body.className = 'daymode' + ' ' + (window.supportsavif ? 'avifsupport' : 'jpegonly')
|
||||
Darkmode.darkIsOn = false
|
||||
}
|
||||
},
|
||||
|
||||
isOn: function() {
|
||||
return Darkmode.darkIsOn
|
||||
},
|
||||
}
|
||||
|
||||
Darkmode.darkIsOn = localStorage.getItem(storageName)
|
||||
|
||||
module.exports = Darkmode
|
|
@ -55,8 +55,8 @@ const Fileinfo = {
|
|||
|
||||
view: function(vnode) {
|
||||
return m('fileinfo', { class: vnode.attrs.slim ? 'slim' : ''}, [
|
||||
m('div.filetitle', [
|
||||
m('span.prefix', this.getPrefix(vnode) + ':'),
|
||||
m('p', [
|
||||
m('span', this.getPrefix(vnode) + ':'),
|
||||
m('a', {
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
|
@ -67,7 +67,7 @@ const Fileinfo = {
|
|||
href: vnode.attrs.file.magnet,
|
||||
}, 'Magnet')
|
||||
: null,
|
||||
m('span', this.getTitle(vnode)),
|
||||
this.getTitle(vnode),
|
||||
]),
|
||||
vnode.attrs.file.meta.torrent
|
||||
&& !vnode.attrs.slim
|
39
nfp_moe/app/footer.js
Normal file
39
nfp_moe/app/footer.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
const m = require('mithril')
|
||||
const PageTree = require('./page_tree')
|
||||
const Authentication = require('./authentication')
|
||||
|
||||
const Footer = {
|
||||
oninit: function(vnode) {
|
||||
this.year = new Date().getFullYear()
|
||||
},
|
||||
|
||||
view: function() {
|
||||
return [
|
||||
m('span', 'Sitemap'),
|
||||
m(m.route.Link, { class: 'root', href: '/' }, 'Home'),
|
||||
PageTree.Tree.map(function(page) {
|
||||
return [
|
||||
m(m.route.Link, { class: 'root', href: '/page/' + page.path }, page.name),
|
||||
(page.children
|
||||
? m('ul', page.children.map(function(subpage) {
|
||||
return m('li', m(m.route.Link, { class: 'child', href: '/page/' + subpage.path }, subpage.name))
|
||||
}))
|
||||
: null),
|
||||
]
|
||||
}),
|
||||
|
||||
!Authentication.currentUser
|
||||
? m(m.route.Link, { class: 'root', href: '/login' }, 'Login')
|
||||
: null,
|
||||
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)',
|
||||
]),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Footer
|
|
@ -1,42 +0,0 @@
|
|||
const m = require('mithril')
|
||||
const Page = require('../api/page.p')
|
||||
const Authentication = require('../authentication')
|
||||
|
||||
const Footer = {
|
||||
oninit: function(vnode) {
|
||||
this.year = new Date().getFullYear()
|
||||
},
|
||||
|
||||
view: function() {
|
||||
return [
|
||||
m('div.footer-filler'),
|
||||
m('div.sitemap', [
|
||||
m('div', 'Sitemap'),
|
||||
m(m.route.Link, { class: 'root', href: '/' }, 'Home'),
|
||||
Page.Tree.map(function(page) {
|
||||
return [
|
||||
m(m.route.Link, { class: 'root', href: '/page/' + page.path }, page.name),
|
||||
(page.children
|
||||
? m('ul', page.children.map(function(subpage) {
|
||||
return m('li', m(m.route.Link, { class: 'child', href: '/page/' + subpage.path }, subpage.name))
|
||||
}))
|
||||
: null),
|
||||
]
|
||||
}),
|
||||
!Authentication.currentUser
|
||||
? m(m.route.Link, { class: 'root', href: '/login' }, 'Login')
|
||||
: null,
|
||||
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'),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Footer
|
|
@ -1,146 +0,0 @@
|
|||
const m = require('mithril')
|
||||
|
||||
const Page = require('../api/page.p')
|
||||
const Article = require('../api/article.p')
|
||||
const Pagination = require('../api/pagination')
|
||||
const Pages = require('../widgets/pages')
|
||||
const Newsitem = require('../widgets/newsitem')
|
||||
|
||||
const Frontpage = {
|
||||
oninit: function(vnode) {
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
this.showLoading = null
|
||||
this.data = {
|
||||
page: null,
|
||||
articles: [],
|
||||
total_articles: 0,
|
||||
featured: null,
|
||||
}
|
||||
this.currentPage = Number(m.route.param('page')) || 1
|
||||
|
||||
if (window.__nfpdata) {
|
||||
this.lastpage = this.currentPage
|
||||
window.__nfpdata = null
|
||||
|
||||
if (this.articles.length === 0) {
|
||||
m.route.set('/')
|
||||
}
|
||||
} else {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
onbeforeupdate: function(vnode) {
|
||||
this.currentPage = Number(m.route.param('page')) || 1
|
||||
if (this.lastpage !== this.currentPage) {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
fetchPage: function(vnode) {
|
||||
this.error = ''
|
||||
this.lastpage = this.currentPage
|
||||
|
||||
if (this.showLoading) {
|
||||
clearTimeout(this.showLoading)
|
||||
}
|
||||
|
||||
this.showLoading = setTimeout(() => {
|
||||
this.showLoading = null
|
||||
this.loading = true
|
||||
m.redraw()
|
||||
}, 150)
|
||||
|
||||
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 Page.getPage(null, this.lastpage)
|
||||
.then((result) => {
|
||||
this.data = result
|
||||
}, (err) => {
|
||||
this.error = err.message
|
||||
})
|
||||
.then(() => {
|
||||
clearTimeout(this.showLoading)
|
||||
this.showLoading = null
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
var deviceWidth = window.innerWidth
|
||||
var bannerPath = this.data.featured && this.data.featured.banner_alt_prefix
|
||||
|
||||
if (bannerPath) {
|
||||
var pixelRatio = window.devicePixelRatio || 1
|
||||
if ((deviceWidth < 720 && pixelRatio <= 1)
|
||||
|| (deviceWidth < 360 && pixelRatio <= 2)) {
|
||||
bannerPath += '_small'
|
||||
} else if ((deviceWidth < 1300 && pixelRatio <= 1)
|
||||
|| (deviceWidth < 650 && pixelRatio <= 2)) {
|
||||
bannerPath += '_medium'
|
||||
} else {
|
||||
bannerPath += '_large'
|
||||
}
|
||||
if (window.supportsavif) {
|
||||
bannerPath += '.avif'
|
||||
} else {
|
||||
bannerPath += '.jpg'
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
(bannerPath
|
||||
? m(m.route.Link, {
|
||||
class: 'frontpage-banner',
|
||||
href: '/article/' + this.data.featured.path,
|
||||
style: { 'background-image': 'url("' + bannerPath + '")' },
|
||||
},
|
||||
this.data.featured.name
|
||||
)
|
||||
: null),
|
||||
m('frontpage', [
|
||||
m('aside.sidebar', [
|
||||
m('div.categories', [
|
||||
m('h4', 'Categories'),
|
||||
m('div',
|
||||
Page.Tree.map(function(page) {
|
||||
return [
|
||||
m(m.route.Link, { class: 'root', href: '/page/' + page.path }, page.name),
|
||||
(page.children.length
|
||||
? m('ul', page.children.map(function(subpage) {
|
||||
return m('li', m(m.route.Link, { class: 'child', href: '/page/' + subpage.path }, subpage.name))
|
||||
}))
|
||||
: null),
|
||||
]
|
||||
})
|
||||
),
|
||||
]),
|
||||
m('div.asunaside', {
|
||||
class: window.supportsavif ? 'avif' : 'jpeg'
|
||||
}),
|
||||
]),
|
||||
m('.frontpage-news', [
|
||||
(this.loading
|
||||
? m('div.loading-spinner')
|
||||
: null),
|
||||
this.data.articles.map(function(article) {
|
||||
return m(Newsitem, { article: article })
|
||||
}),
|
||||
m(Pages, {
|
||||
base: '/',
|
||||
total: this.data.total_articles,
|
||||
page: this.currentPage,
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Frontpage
|
115
nfp_moe/app/header.js
Normal file
115
nfp_moe/app/header.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
const m = require('mithril')
|
||||
const Authentication = require('./authentication')
|
||||
const PageTree = require('./page_tree')
|
||||
|
||||
const DarkModeStorageName = 'nfp_sites_darkmode'
|
||||
|
||||
const Menu = {
|
||||
oninit: function(vnode) {
|
||||
this.currentActive = 'home'
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
this.onbeforeupdate()
|
||||
|
||||
if (!PageTree.Tree.length) {
|
||||
this.refreshTree()
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
onbeforeupdate: function() {
|
||||
this.darkIsOn = localStorage.getItem(DarkModeStorageName)
|
||||
|
||||
let currentPath = m.route.get()
|
||||
|
||||
if (currentPath === '/') this.currentActive = 'home'
|
||||
else if (currentPath === '/login') this.currentActive = 'login'
|
||||
else if (currentPath && currentPath.startsWith('/page')) this.currentActive = currentPath.slice(currentPath.lastIndexOf('/') + 1)
|
||||
},
|
||||
|
||||
refreshTree: function(vnode) {
|
||||
this.loading = true
|
||||
this.error = ''
|
||||
|
||||
PageTree.refreshTree()
|
||||
.catch((err) => {
|
||||
this.error = 'Error while getting menu tree: ' + err.message + '. Click here to try again.'
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
logOut: function() {
|
||||
Authentication.clearToken()
|
||||
m.route.set('/')
|
||||
},
|
||||
|
||||
toggleDarkMode: function() {
|
||||
this.darkIsOn = !this.darkIsOn
|
||||
if (this.darkIsOn) {
|
||||
localStorage.setItem(DarkModeStorageName, true)
|
||||
} else {
|
||||
localStorage.removeItem(DarkModeStorageName)
|
||||
}
|
||||
document.body.className = (this.darkIsOn ? 'darkmode ' : 'daymode ')
|
||||
+ (window.supportsavif ? 'avifsupport' : 'jpegonly')
|
||||
},
|
||||
|
||||
view: function() {
|
||||
return [
|
||||
m('header', [
|
||||
m('div.inside', [
|
||||
m(m.route.Link,
|
||||
{ href: '/', class: 'logo' },
|
||||
m('h1', 'NFP Moe')
|
||||
),
|
||||
m('aside', [
|
||||
Authentication.currentUser
|
||||
? [
|
||||
m('p', [
|
||||
'Welcome ' + Authentication.currentUser.name + '. ',
|
||||
m('button', { onclick: this.logOut }, '(Log out)'),
|
||||
]),
|
||||
m('div.actions', [
|
||||
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.rank < 100, href: '/admin/staff' }, 'Staff'),
|
||||
])
|
||||
]
|
||||
: null,
|
||||
m('button',
|
||||
{ onclick: this.toggleDarkMode.bind(this) },
|
||||
this.darkIsOn ? 'Day mode' : 'Night mode'
|
||||
),
|
||||
])
|
||||
]),
|
||||
]),
|
||||
m('nav', [
|
||||
m('div.inside', [
|
||||
m(m.route.Link, {
|
||||
href: '/',
|
||||
class: this.currentActive === 'home' ? 'active' : '',
|
||||
}, 'Home'),
|
||||
this.loading ? m('div.loading-spinner') : null,
|
||||
PageTree.Tree.map((page) => {
|
||||
let className = ''
|
||||
if (this.currentActive === page.path) {
|
||||
className += 'active '
|
||||
}
|
||||
return m(m.route.Link, {
|
||||
href: '/page/' + page.path,
|
||||
class: className,
|
||||
}, page.name)
|
||||
}),
|
||||
])
|
||||
]),
|
||||
this.error ? m('div.error', { onclick: this.refreshTree.bind(this) }, this.error) : null,
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Menu
|
|
@ -1,6 +1,11 @@
|
|||
require('./polyfill')
|
||||
|
||||
const m = require('mithril')
|
||||
const Authentication = require('./authentication')
|
||||
const AdminResolver = require('./admin_loader')
|
||||
const Header = require('./header')
|
||||
const Footer = require('./footer')
|
||||
const Login = require('./site_login')
|
||||
const SitePage = require('./site_page')
|
||||
const SiteArticle = require('./site_article')
|
||||
window.m = m
|
||||
|
||||
m.route.setOrig = m.route.set
|
||||
|
@ -15,105 +20,13 @@ m.route.link = function(vnode){
|
|||
window.scrollTo(0, 0)
|
||||
}
|
||||
|
||||
const Authentication = require('./authentication')
|
||||
|
||||
m.route.prefix = ''
|
||||
window.adminRoutes = {}
|
||||
let loadingAdmin = false
|
||||
let loadedAdmin = false
|
||||
let loaded = 0
|
||||
let elements = []
|
||||
|
||||
const onLoaded = function() {
|
||||
loaded++
|
||||
if (loaded < 2) return
|
||||
|
||||
Authentication.setAdmin(Authentication.currentUser && Authentication.currentUser.rank >= 10)
|
||||
loadedAdmin = true
|
||||
m.route.set(m.route.get())
|
||||
}
|
||||
|
||||
const onError = function(a, b, c) {
|
||||
elements.forEach(function(x) { x.remove() })
|
||||
loadedAdmin = loadingAdmin = false
|
||||
loaded = 0
|
||||
m.route.set('/logout')
|
||||
}
|
||||
|
||||
const loadAdmin = function(user) {
|
||||
if (loadingAdmin) {
|
||||
if (loadedAdmin) {
|
||||
Authentication.setAdmin(user && user.rank >= 10)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!user || user.rank < 10) return
|
||||
|
||||
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?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?token=' + token)
|
||||
element.onload = onLoaded
|
||||
element.onerror = onError
|
||||
document.body.appendChild(element)
|
||||
|
||||
element = document.createElement('script')
|
||||
elements.push(element)
|
||||
element.setAttribute('type', 'text/javascript')
|
||||
element.setAttribute('src', '/assets/editor.js')
|
||||
element.onload = onLoaded
|
||||
element.onerror = onError
|
||||
document.body.appendChild(element)
|
||||
}
|
||||
|
||||
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,
|
||||
'/': SitePage, // Frontpage
|
||||
'/login': Login,
|
||||
'/logout': Logout,
|
||||
'/page/:id': Page,
|
||||
'/article/:id': Article,
|
||||
'/page/:id': SitePage,
|
||||
'/article/:id': SiteArticle,
|
||||
'/admin/:path': AdminResolver,
|
||||
'/admin/:path/:id': AdminResolver,
|
||||
}
|
||||
|
@ -128,8 +41,8 @@ AVIF.onload = AVIF.onerror = function () {
|
|||
window.supportsavif = (AVIF.height === 2)
|
||||
document.body.className = document.body.className + ' ' + (window.supportsavif ? 'avifsupport' : 'jpegonly')
|
||||
|
||||
m.route(mainRoot, '/', allRoutes)
|
||||
m.mount(menuRoot, Menu)
|
||||
m.mount(footerRoot, Footer)
|
||||
m.mount(document.getElementById('header'), Header)
|
||||
m.route(document.getElementById('main'), '/', allRoutes)
|
||||
m.mount(document.getElementById('footer'), Footer)
|
||||
}
|
||||
AVIF.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
const m = require('mithril')
|
||||
const Authentication = require('../authentication')
|
||||
const Api = require('../api/common')
|
||||
|
||||
const Login = {
|
||||
loading: false,
|
||||
redirect: '',
|
||||
error: '',
|
||||
|
||||
oninit: function(vnode) {
|
||||
Login.redirect = vnode.attrs.redirect || ''
|
||||
if (Authentication.currentUser) return m.route.set('/')
|
||||
Login.error = ''
|
||||
|
||||
this.username = ''
|
||||
this.password = ''
|
||||
},
|
||||
|
||||
oncreate: function() {
|
||||
if (Authentication.currentUser) return
|
||||
},
|
||||
|
||||
loginuser: function(vnode, e) {
|
||||
e.preventDefault()
|
||||
if (!this.username) {
|
||||
Login.error = 'Email is missing'
|
||||
} else if (!this.password) {
|
||||
Login.error = 'Password is missing'
|
||||
} else {
|
||||
Login.error = ''
|
||||
}
|
||||
if (Login.error) return
|
||||
|
||||
Login.loading = true
|
||||
|
||||
Api.sendRequest({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/login',
|
||||
body: {
|
||||
email: this.username,
|
||||
password: this.password,
|
||||
},
|
||||
})
|
||||
.then(function(result) {
|
||||
if (!result.token) {
|
||||
return Promise.reject(new Error('Server authentication down.'))
|
||||
}
|
||||
Authentication.updateToken(result.token)
|
||||
m.route.set(Login.redirect || '/')
|
||||
})
|
||||
.catch(function(error) {
|
||||
Login.error = 'Error while logging into NFP! ' + error.message
|
||||
vnode.state.password = ''
|
||||
})
|
||||
.then(function () {
|
||||
Login.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m('div.login-wrapper', [
|
||||
m('div.login-icon'),
|
||||
m('article.login', [
|
||||
m('header', [
|
||||
m('h1', 'NFP.moe login'),
|
||||
]),
|
||||
m('div.content', [
|
||||
m('h5', 'Please login to access restricted area'),
|
||||
Login.error ? m('div.error', Login.error) : null,
|
||||
Login.loading ? m('div.loading-spinner') : null,
|
||||
m('form', {
|
||||
hidden: Login.loading,
|
||||
onsubmit: this.loginuser.bind(this, vnode),
|
||||
}, [
|
||||
m('label', 'Email'),
|
||||
m('input', {
|
||||
type: 'text',
|
||||
value: this.username,
|
||||
oninput: function(e) { vnode.state.username = e.currentTarget.value },
|
||||
}),
|
||||
m('label', 'Password'),
|
||||
m('input', {
|
||||
type: 'password',
|
||||
value: this.password,
|
||||
oninput: function(e) { vnode.state.password = e.currentTarget.value },
|
||||
}),
|
||||
m('input', {
|
||||
type: 'submit',
|
||||
value: 'Login',
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Login
|
|
@ -1,15 +0,0 @@
|
|||
const m = require('mithril')
|
||||
const Authentication = require('../authentication')
|
||||
|
||||
const Logout = {
|
||||
oninit: function() {
|
||||
Authentication.clearToken()
|
||||
m.route.set('/')
|
||||
},
|
||||
|
||||
view: function() {
|
||||
return m('div.loading-spinner')
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Logout
|
50
nfp_moe/app/media.js
Normal file
50
nfp_moe/app/media.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
export function generateSource(item, cover) {
|
||||
if (!item) return
|
||||
|
||||
if (item.media_alt_prefix) {
|
||||
item.pictureFallback = item.media_alt_prefix + '_small.jpg'
|
||||
item.pictureJpeg = item.media_alt_prefix + '_small.jpg' + ' 720w, '
|
||||
+ item.media_alt_prefix + '_medium.jpg' + ' 1300w, '
|
||||
+ item.media_alt_prefix + '_large.jpg 1920w'
|
||||
item.pictureAvif = item.media_alt_prefix + '_small.avif' + ' 720w, '
|
||||
+ item.media_alt_prefix + '_medium.avif' + ' 1300w, '
|
||||
+ item.media_alt_prefix + '_large.avif 1920w'
|
||||
|
||||
item.pictureCover = cover
|
||||
} else {
|
||||
item.pictureFallback = null
|
||||
item.pictureJpeg = null
|
||||
item.pictureAvif = null
|
||||
item.pictureCover = null
|
||||
}
|
||||
}
|
||||
|
||||
export function getBannerImage(item, prefix) {
|
||||
if (!item || !item.banner_alt_prefix) return null
|
||||
|
||||
let out = {
|
||||
path: prefix + item.path,
|
||||
name: item.name,
|
||||
original: item.banner_path,
|
||||
banner: item.banner_alt_prefix
|
||||
}
|
||||
|
||||
var deviceWidth = window.innerWidth
|
||||
var pixelRatio = window.devicePixelRatio || 1
|
||||
if ((deviceWidth < 720 && pixelRatio <= 1)
|
||||
|| (deviceWidth < 360 && pixelRatio <= 2)) {
|
||||
out.banner += '_small'
|
||||
} else if ((deviceWidth < 1300 && pixelRatio <= 1)
|
||||
|| (deviceWidth < 650 && pixelRatio <= 2)) {
|
||||
out.banner += '_medium'
|
||||
} else {
|
||||
out.banner += '_large'
|
||||
}
|
||||
if (window.supportsavif) {
|
||||
out.banner += '.avif'
|
||||
} else {
|
||||
out.banner += '.jpg'
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
const m = require('mithril')
|
||||
const Authentication = require('../authentication')
|
||||
const Darkmode = require('../darkmode')
|
||||
const Page = require('../api/page.p')
|
||||
|
||||
const Menu = {
|
||||
currentActive: 'home',
|
||||
error: '',
|
||||
loading: false,
|
||||
|
||||
onbeforeupdate: function() {
|
||||
let currentPath = m.route.get()
|
||||
if (currentPath === '/') Menu.currentActive = 'home'
|
||||
else if (currentPath === '/login') Menu.currentActive = 'login'
|
||||
else Menu.currentActive = currentPath
|
||||
},
|
||||
|
||||
oninit: function(vnode) {
|
||||
Menu.onbeforeupdate()
|
||||
|
||||
if (Page.Tree.length) return
|
||||
|
||||
Menu.loading = true
|
||||
|
||||
Page.refreshTree()
|
||||
.catch(function(err) {
|
||||
Menu.error = err.message
|
||||
})
|
||||
.then(function() {
|
||||
Menu.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function() {
|
||||
console.log('menu view', Boolean(Authentication.currentUser))
|
||||
return [
|
||||
m('div.top', [
|
||||
m(m.route.Link,
|
||||
{ href: '/', class: 'logo' },
|
||||
m('h2', 'NFP Moe')
|
||||
),
|
||||
m('aside', Authentication.currentUser ? [
|
||||
m('p', [
|
||||
'Welcome ' + Authentication.currentUser.name,
|
||||
m(m.route.Link, { href: '/logout' }, 'Logout'),
|
||||
(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.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.rank < 100, href: '/admin/staff' }, 'Staff'),
|
||||
])
|
||||
: (Authentication.currentUser.rank > 10 ? m('div.loading-spinner') : null)
|
||||
),
|
||||
] : (Darkmode.darkIsOn
|
||||
? m('button.dark', { onclick: Darkmode.setDarkMode.bind(Darkmode, false) }, 'Day mode')
|
||||
: m('button.dark', { onclick: Darkmode.setDarkMode.bind(Darkmode, true) }, 'Night mode')
|
||||
)
|
||||
),
|
||||
]),
|
||||
m('nav', [
|
||||
m(m.route.Link, {
|
||||
href: '/',
|
||||
class: Menu.currentActive === 'home' ? 'active' : '',
|
||||
}, 'Home'),
|
||||
Menu.loading ? m('div.loading-spinner') : Page.Tree.map(function(page) {
|
||||
if (page.children) {
|
||||
return m('div.hassubmenu', [
|
||||
m(m.route.Link, {
|
||||
href: '/page/' + page.path,
|
||||
class: Menu.currentActive === ('/page/' + page.path) ? 'active' : '',
|
||||
}, page.name),
|
||||
])
|
||||
}
|
||||
return m(m.route.Link, {
|
||||
href: '/page/' + page.path,
|
||||
class: Menu.currentActive === ('/page/' + page.path) ? 'active' : '',
|
||||
}, page.name)
|
||||
}),
|
||||
]),
|
||||
Menu.error ? m('div.menuerror', Menu.error) : null,
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Menu
|
|
@ -1,4 +1,4 @@
|
|||
const common = require('./common')
|
||||
const api = require('./api')
|
||||
|
||||
const Tree = window.__nfptree && window.__nfptree.tree || []
|
||||
const TreeMap = new Map()
|
||||
|
@ -33,15 +33,8 @@ exports.getFlatTree = function() {
|
|||
return arr
|
||||
}
|
||||
|
||||
exports.getPage = function(path, page) {
|
||||
return common.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/' + (path ? 'pages/' + path : 'frontpage') + '?page=' + (page || 1),
|
||||
})
|
||||
}
|
||||
|
||||
exports.refreshTree = function() {
|
||||
return common.sendRequest({
|
||||
return api.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/pagetree',
|
||||
})
|
|
@ -1,233 +0,0 @@
|
|||
const m = require('mithril')
|
||||
const ApiPage = require('../api/page.p')
|
||||
const Article = require('../api/article.p')
|
||||
const pagination = require('../api/pagination')
|
||||
const Authentication = require('../authentication')
|
||||
const Newsentry = require('../widgets/newsentry')
|
||||
const Pages = require('../widgets/pages')
|
||||
|
||||
const Page = {
|
||||
oninit: function(vnode) {
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
this.showLoading = null
|
||||
this.data = {
|
||||
page: null,
|
||||
articles: [],
|
||||
total_articles: 0,
|
||||
featured: null,
|
||||
}
|
||||
this.children = []
|
||||
this.currentPage = Number(m.route.param('page')) || 1
|
||||
|
||||
if (window.__nfpdata) {
|
||||
this.path = m.route.param('id')
|
||||
this.data = window.__nfpdata
|
||||
|
||||
window.__nfpdata = null
|
||||
window.__nfpsubdata = null
|
||||
} else {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
onbeforeupdate: function(vnode) {
|
||||
this.currentPage = Number(m.route.param('page')) || 1
|
||||
|
||||
if (this.path !== m.route.param('id') || this.currentPage !== this.lastpage) {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
fetchPage: function(vnode) {
|
||||
this.error = ''
|
||||
this.lastpage = this.currentPage
|
||||
this.path = m.route.param('id')
|
||||
|
||||
if (this.showLoading) {
|
||||
clearTimeout(this.showLoading)
|
||||
}
|
||||
|
||||
if (this.data.page) {
|
||||
this.showLoading = setTimeout(() => {
|
||||
this.showLoading = null
|
||||
this.loading = true
|
||||
m.redraw()
|
||||
}, 150)
|
||||
} else {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
this.children = ApiPage.TreeMap.get(this.path)
|
||||
this.children = this.children && this.children.children || []
|
||||
|
||||
ApiPage.getPage(this.path, this.lastpage)
|
||||
.then((result) => {
|
||||
this.data = result
|
||||
|
||||
if (!this.data.page) {
|
||||
this.error = 'Page not found'
|
||||
return
|
||||
}
|
||||
|
||||
if (this.data.page.media_alt_prefix) {
|
||||
this.data.page.pictureFallback = this.data.page.media_alt_prefix + '_small.jpg'
|
||||
this.data.page.pictureJpeg = this.data.page.media_alt_prefix + '_small.jpg' + ' 720w, '
|
||||
+ this.data.page.media_alt_prefix + '_medium.jpg' + ' 1300w, '
|
||||
+ this.data.page.media_alt_prefix + '_large.jpg 1920w'
|
||||
this.data.page.pictureAvif = this.data.page.media_alt_prefix + '_small.avif' + ' 720w, '
|
||||
+ this.data.page.media_alt_prefix + '_medium.avif' + ' 1300w, '
|
||||
+ this.data.page.media_alt_prefix + '_large.avif 1920w'
|
||||
|
||||
this.data.page.pictureCover = '(max-width: 840px) calc(100vw - 82px), '
|
||||
+ '758px'
|
||||
} else {
|
||||
this.data.page.pictureFallback = null
|
||||
this.data.page.pictureJpeg = null
|
||||
this.data.page.pictureAvif = null
|
||||
this.data.page.pictureCover = null
|
||||
}
|
||||
|
||||
if (this.lastpage !== 1) {
|
||||
document.title = 'Page ' + this.lastpage + ' - ' + this.data.page.name + ' - NFP Moe'
|
||||
} else {
|
||||
document.title = this.data.page.name + ' - NFP Moe'
|
||||
}
|
||||
}, (err) => {
|
||||
this.error = err.message
|
||||
})
|
||||
.then(() => {
|
||||
clearTimeout(this.showLoading)
|
||||
this.showLoading = null
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
let page = this.data.page
|
||||
let bannerPath = ''
|
||||
|
||||
return ([
|
||||
this.loading
|
||||
? m('article.page', m('div.loading-spinner'))
|
||||
: null,
|
||||
!this.loading && this.error
|
||||
? m('div.error-wrapper', m('div.error', {
|
||||
onclick: function() {
|
||||
vnode.state.error = ''
|
||||
vnode.state.fetchPage(vnode)
|
||||
},
|
||||
}, 'Page error: ' + this.error))
|
||||
: null,
|
||||
!this.loading && !this.error
|
||||
? m('article.page', [
|
||||
bannerPath
|
||||
? m('.div.page-banner', { style: { 'background-image': 'url("' + bannerPath + '")' } } )
|
||||
: null,
|
||||
m('div.goback', ['« ', m(m.route.Link, {
|
||||
href: page.parent_path
|
||||
? '/page/' + page.parent_path
|
||||
: '/'
|
||||
}, page.parent_name || 'Home')]),
|
||||
m('header', m('h1', page.name)),
|
||||
m('.container', {
|
||||
class: this.children.length ? 'multi' : '',
|
||||
}, [
|
||||
this.children.length
|
||||
? m('aside.sidebar', [
|
||||
m('h4', 'View ' + page.name + ':'),
|
||||
this.children.map(function(page) {
|
||||
return m(m.route.Link, { href: '/page/' + page.path }, page.name)
|
||||
}),
|
||||
])
|
||||
: null,
|
||||
page.content
|
||||
? m('.fr-view', [
|
||||
page.pictureFallback
|
||||
? m('a.cover', {
|
||||
rel: 'noopener',
|
||||
href: page.media_path,
|
||||
}, [
|
||||
m('picture', [
|
||||
m('source', {
|
||||
srcset: page.pictureAvif,
|
||||
sizes: page.pictureCover,
|
||||
type: 'image/avif',
|
||||
}),
|
||||
m('img', {
|
||||
srcset: page.pictureJpeg,
|
||||
sizes: page.pictureCover,
|
||||
alt: 'Image for news item ' + page.name,
|
||||
src: page.pictureFallback,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
: null,
|
||||
page.content.blocks.map(block => {
|
||||
return m(EditorBlock, { block: block })
|
||||
}),
|
||||
this.data.articles.length && page.content
|
||||
? m('aside.news', [
|
||||
m('h4', 'Latest posts under ' + page.name + ':'),
|
||||
this.data.articles.map(function(article) {
|
||||
return m(Newsentry, { article: article })
|
||||
}),
|
||||
m(Pages, {
|
||||
base: '/page/' + page.path,
|
||||
total: this.data.total_articles,
|
||||
page: this.currentPage,
|
||||
}),
|
||||
])
|
||||
: null,
|
||||
])
|
||||
: this.data.articles.length
|
||||
? m('aside.news.single',
|
||||
[
|
||||
page.pictureFallback
|
||||
? m('a', {
|
||||
rel: 'noopener',
|
||||
href: page.media_path,
|
||||
}, [
|
||||
m('picture.page-cover', [
|
||||
m('source', {
|
||||
srcset: page.pictureAvif,
|
||||
sizes: page.pictureCover,
|
||||
type: 'image/avif',
|
||||
}),
|
||||
m('img', {
|
||||
srcset: page.pictureJpeg,
|
||||
sizes: page.pictureCover,
|
||||
alt: 'Cover image for ' + page.name,
|
||||
src: page.pictureFallback,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
: null,
|
||||
m('h4', 'Latest posts under ' + page.name + ':'),
|
||||
this.data.articles.map(function(article) {
|
||||
return m(Newsentry, { article: article })
|
||||
}),
|
||||
m(Pages, {
|
||||
base: '/page/' + page.path,
|
||||
total: this.data.total_articles,
|
||||
page: this.currentPage,
|
||||
}),
|
||||
])
|
||||
: page.media
|
||||
? m('img.page-cover.single', { src: page.media.medium_url, alt: 'Cover image for ' + page.name } )
|
||||
: null,
|
||||
]),
|
||||
Authentication.currentUser
|
||||
? m('div.admin-actions', [
|
||||
m('span', 'Admin controls:'),
|
||||
m(m.route.Link, { href: '/admin/pages/' + page.path }, 'Edit page'),
|
||||
])
|
||||
: null,
|
||||
])
|
||||
: null,
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Page
|
39
nfp_moe/app/paginator.js
Normal file
39
nfp_moe/app/paginator.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
const Paginator = {
|
||||
view: function(vnode) {
|
||||
let total = vnode.attrs.total
|
||||
let currentPage = vnode.attrs.page
|
||||
let perPage = vnode.attrs.perPage || 10
|
||||
let maxPage = total / perPage + 1
|
||||
|
||||
if (total <= perPage) return null
|
||||
|
||||
return m('paginator', [
|
||||
currentPage > 1
|
||||
? [
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base,
|
||||
}, 'First'),
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base + (currentPage > 2
|
||||
? '?page=' + (currentPage - 1)
|
||||
: ''
|
||||
),
|
||||
}, 'Previous'),
|
||||
]
|
||||
: m('div'),
|
||||
m('div', 'Page ' + currentPage),
|
||||
currentPage < maxPage
|
||||
? [
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base + '?page=' + (currentPage + 1),
|
||||
}, 'Next'),
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base + '?page=' + maxPage,
|
||||
}, 'Last')
|
||||
]
|
||||
: m('div'),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Paginator
|
|
@ -1,8 +0,0 @@
|
|||
if (!String.prototype.endsWith) {
|
||||
String.prototype.endsWith = function(search, this_len) {
|
||||
if (this_len === undefined || this_len > this.length) {
|
||||
this_len = this.length;
|
||||
}
|
||||
return this.substring(this_len - search.length, this_len) === search;
|
||||
};
|
||||
}
|
155
nfp_moe/app/site_article.js
Normal file
155
nfp_moe/app/site_article.js
Normal file
|
@ -0,0 +1,155 @@
|
|||
const m = require('mithril')
|
||||
const Article = require('./article')
|
||||
const api = require('./api')
|
||||
const media = require('./media')
|
||||
|
||||
window.LoadComments = false
|
||||
window.HyvorLoaded = false
|
||||
window.HYVOR_TALK_WEBSITE = 7544
|
||||
|
||||
const SiteArticle = {
|
||||
oninit: function(vnode) {
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
this.showLoading = null
|
||||
this.data = {
|
||||
article: null,
|
||||
files: [],
|
||||
}
|
||||
this.showcomments = false
|
||||
|
||||
if (window.__nfpdata) {
|
||||
this.path = m.route.param('id')
|
||||
this.data.article = window.__nfpdata
|
||||
window.__nfpdata = null
|
||||
} else {
|
||||
this.fetchArticle(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
onbeforeupdate: function(vnode) {
|
||||
if (this.path !== m.route.param('id')) {
|
||||
this.fetchArticle(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
fetchArticle: function(vnode) {
|
||||
this.error = ''
|
||||
this.path = m.route.param('id')
|
||||
this.showcomments = false
|
||||
|
||||
if (this.showLoading) {
|
||||
clearTimeout(this.showLoading)
|
||||
}
|
||||
|
||||
if (this.data.article) {
|
||||
this.showLoading = setTimeout(() => {
|
||||
this.showLoading = null
|
||||
this.loading = true
|
||||
m.redraw()
|
||||
}, 150)
|
||||
} else {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
api.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/articles/' + this.path,
|
||||
})
|
||||
.then((result) => {
|
||||
this.data = result
|
||||
|
||||
if (this.data.article.media_alt_prefix) {
|
||||
this.data.article.pictureFallback = this.data.article.media_alt_prefix + '_small.jpg'
|
||||
this.data.article.pictureJpeg = this.data.article.media_alt_prefix + '_small.jpg' + ' 720w, '
|
||||
+ this.data.article.media_alt_prefix + '_medium.jpg' + ' 1300w, '
|
||||
+ this.data.article.media_alt_prefix + '_large.jpg 1920w'
|
||||
this.data.article.pictureAvif = this.data.article.media_alt_prefix + '_small.avif' + ' 720w, '
|
||||
+ this.data.article.media_alt_prefix + '_medium.avif' + ' 1300w, '
|
||||
+ this.data.article.media_alt_prefix + '_large.avif 1920w'
|
||||
|
||||
this.data.article.pictureCover = '(max-width: 840px) calc(100vw - 82px), '
|
||||
+ '758px'
|
||||
} else {
|
||||
this.data.article.pictureFallback = null
|
||||
this.data.article.pictureJpeg = null
|
||||
this.data.article.pictureAvif = null
|
||||
this.data.article.pictureCover = null
|
||||
}
|
||||
|
||||
if (!this.data.article) {
|
||||
this.error = 'Article not found'
|
||||
}
|
||||
}, (err) => {
|
||||
this.error = err.message
|
||||
})
|
||||
.then(() => {
|
||||
clearTimeout(this.showLoading)
|
||||
this.showLoading = null
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
let article = this.data.article
|
||||
let banner = media.getBannerImage(article, '/article/')
|
||||
|
||||
return [
|
||||
this.loading
|
||||
? m('div.loading-spinner')
|
||||
: null,
|
||||
!this.loading && this.error
|
||||
? m('div.wrapper', m('div.error', {
|
||||
onclick: () => {
|
||||
this.error = ''
|
||||
this.fetchPage(vnode)
|
||||
},
|
||||
}, 'Page error: ' + this.error + '. Click here to try again'))
|
||||
: null,
|
||||
/*(banner
|
||||
? m('a.page-banner', {
|
||||
href: banner.original,
|
||||
target: '_blank',
|
||||
style: { 'background-image': 'url("' + banner.banner + '")' },
|
||||
},
|
||||
)
|
||||
: null),*/
|
||||
(article
|
||||
? m('.inside.vertical', [
|
||||
m('div.page-goback', ['« ', m(m.route.Link, {
|
||||
href: article.page_path
|
||||
? '/page/' + article.page_path
|
||||
: '/'
|
||||
}, article.page_name || 'Home')]
|
||||
),
|
||||
article ? m(Article, { full: true, files: this.data.files, article: article }) : null,
|
||||
window.LoadComments
|
||||
? m('div#hyvor-talk-view', { oncreate: function() {
|
||||
window.HYVOR_TALK_CONFIG = {
|
||||
url: false,
|
||||
id: article.path,
|
||||
loadMode: scroll,
|
||||
}
|
||||
if (!window.HyvorLoaded) {
|
||||
window.HyvorLoaded = true
|
||||
var s = document.createElement('script')
|
||||
s.src = 'https://talk.hyvor.com/web-api/embed.js';
|
||||
s.type = 'text/javascript'
|
||||
// s.setAttribute('crossorigin', '')
|
||||
// s.setAttribute('data-timestamp', +new Date());
|
||||
document.body.appendChild(s);
|
||||
} else {
|
||||
hyvor_talk.reload()
|
||||
}
|
||||
}}, m('div.loading-spinner'))
|
||||
: m('button', {
|
||||
onclick: function() { window.LoadComments = true },
|
||||
}, 'Open comment discussion'),
|
||||
])
|
||||
: null),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = SiteArticle
|
90
nfp_moe/app/site_login.js
Normal file
90
nfp_moe/app/site_login.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
const m = require('mithril')
|
||||
const Authentication = require('./authentication')
|
||||
const api = require('./api')
|
||||
|
||||
const Login = {
|
||||
oninit: function(vnode) {
|
||||
this.redirect = vnode.attrs.redirect || ''
|
||||
if (Authentication.currentUser) return m.route.set('/')
|
||||
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
this.username = ''
|
||||
this.password = ''
|
||||
},
|
||||
|
||||
oncreate: function() {
|
||||
if (Authentication.currentUser) return
|
||||
},
|
||||
|
||||
loginuser: function(vnode, e) {
|
||||
e.preventDefault()
|
||||
if (!this.username) {
|
||||
this.error = 'Email is missing'
|
||||
} else if (!this.password) {
|
||||
this.error = 'Password is missing'
|
||||
} else {
|
||||
this.error = ''
|
||||
}
|
||||
if (this.error) return
|
||||
|
||||
this.loading = true
|
||||
|
||||
api.sendRequest({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/login',
|
||||
body: {
|
||||
email: this.username,
|
||||
password: this.password,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
if (!result.token) {
|
||||
return Promise.reject(new Error('Server authentication down.'))
|
||||
}
|
||||
Authentication.updateToken(result.token)
|
||||
m.route.set(this.redirect || '/')
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = 'Error while logging into NFP! ' + error.message
|
||||
vnode.state.password = ''
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m('div.wrapper', [
|
||||
this.loading ? m('div.loading-spinner') : null,
|
||||
m('form.inside.login', {
|
||||
hidden: this.loading,
|
||||
onsubmit: this.loginuser.bind(this, vnode),
|
||||
}, [
|
||||
m('div.title', 'NFP.moe login'),
|
||||
this.error ? m('div.error', this.error) : null,
|
||||
m('label', 'Email'),
|
||||
m('input', {
|
||||
type: 'text',
|
||||
value: this.username,
|
||||
oninput: (e) => { this.username = e.currentTarget.value },
|
||||
}),
|
||||
m('label', 'Password'),
|
||||
m('input', {
|
||||
type: 'password',
|
||||
value: this.password,
|
||||
oninput: (e) => { this.password = e.currentTarget.value },
|
||||
}),
|
||||
m('input', {
|
||||
type: 'submit',
|
||||
value: 'Log in',
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Login
|
224
nfp_moe/app/site_page.js
Normal file
224
nfp_moe/app/site_page.js
Normal file
|
@ -0,0 +1,224 @@
|
|||
const m = require('mithril')
|
||||
const api = require('./api')
|
||||
const PageTree = require('./page_tree')
|
||||
const Paginator = require('./paginator')
|
||||
// const Authentication = require('./authentication')
|
||||
const Article = require('./article')
|
||||
const Articleslim = require('./article_slim')
|
||||
const media = require('./media')
|
||||
|
||||
const ArticlesPerPage = 10
|
||||
|
||||
const SitePage = {
|
||||
oninit: function(vnode) {
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
this.showLoading = null
|
||||
this.data = {
|
||||
page: null,
|
||||
articles: [],
|
||||
total_articles: 0,
|
||||
featured: null,
|
||||
}
|
||||
this.children = []
|
||||
this.currentPage = Number(m.route.param('page')) || 1
|
||||
|
||||
if (window.__nfpdata) {
|
||||
this.path = m.route.param('id')
|
||||
this.data = window.__nfpdata
|
||||
|
||||
window.__nfpdata = null
|
||||
window.__nfpsubdata = null
|
||||
} else {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
onbeforeupdate: function(vnode) {
|
||||
this.currentPage = Number(m.route.param('page')) || 1
|
||||
|
||||
if (this.path !== m.route.param('id') || this.currentPage !== this.lastpage) {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
},
|
||||
|
||||
fetchPage: function(vnode) {
|
||||
this.error = ''
|
||||
this.lastpage = this.currentPage
|
||||
this.path = m.route.param('id')
|
||||
|
||||
if (this.showLoading) {
|
||||
clearTimeout(this.showLoading)
|
||||
}
|
||||
|
||||
if (this.data.page) {
|
||||
this.showLoading = setTimeout(() => {
|
||||
this.showLoading = null
|
||||
this.loading = true
|
||||
m.redraw()
|
||||
}, 300)
|
||||
} else {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
if (this.path) {
|
||||
this.children = PageTree.TreeMap.get(this.path)
|
||||
this.children = this.children && this.children.children || []
|
||||
} else {
|
||||
this.children = PageTree.Tree
|
||||
}
|
||||
|
||||
api.sendRequest({
|
||||
method: 'GET',
|
||||
url: '/api/' + (this.path ? 'pages/' + this.path : 'frontpage') + '?page=' + (this.lastpage || 1),
|
||||
})
|
||||
.then((result) => {
|
||||
this.data = result
|
||||
|
||||
if (!this.data.page && this.path) {
|
||||
this.error = 'Page not found'
|
||||
return
|
||||
}
|
||||
|
||||
let title = 'Page not found - NFP Moe - Anime/Manga translation group'
|
||||
if (this.data.page) {
|
||||
title = this.data.page.name + ' - NFP Moe'
|
||||
} else if (!this.path) {
|
||||
title = 'NFP Moe - Anime/Manga translation group'
|
||||
}
|
||||
|
||||
media.generateSource(this.data.page,
|
||||
'(max-width: 840px) calc(100vw - 82px), '
|
||||
+ '758px')
|
||||
|
||||
if (this.lastpage !== 1) {
|
||||
document.title = 'Page ' + this.lastpage + ' - ' + title
|
||||
} else {
|
||||
document.title = title
|
||||
}
|
||||
}, (err) => {
|
||||
this.error = err.message
|
||||
})
|
||||
.then(() => {
|
||||
clearTimeout(this.showLoading)
|
||||
this.showLoading = null
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
let page = this.data.page
|
||||
let featuredBanner = media.getBannerImage(this.data.featured, '/article/')
|
||||
let pageBanner = media.getBannerImage(this.data.page, '/page/')
|
||||
let paginatorMaxPath = Math.floor(this.data.total_articles / ArticlesPerPage) + 1
|
||||
let paginatorPrefix = page ? '/page/' + page.path : '/'
|
||||
|
||||
return ([
|
||||
this.loading
|
||||
? m('div.loading-spinner')
|
||||
: null,
|
||||
!this.loading && this.error
|
||||
? m('div.wrapper', m('div.error', {
|
||||
onclick: () => {
|
||||
this.error = ''
|
||||
this.fetchPage(vnode)
|
||||
},
|
||||
}, 'Page error: ' + this.error + '. Click here to try again'))
|
||||
: null,
|
||||
(featuredBanner
|
||||
? m(m.route.Link, {
|
||||
class: 'page-banner',
|
||||
href: featuredBanner.path,
|
||||
style: { 'background-image': 'url("' + featuredBanner.banner + '")' },
|
||||
},
|
||||
m('div.inside', m('div.page-banner-title', featuredBanner.name))
|
||||
)
|
||||
: null),
|
||||
(!featuredBanner && pageBanner
|
||||
? m('a.page-banner', {
|
||||
href: pageBanner.original,
|
||||
target: '_blank',
|
||||
style: { 'background-image': 'url("' + pageBanner.banner + '")' },
|
||||
},
|
||||
)
|
||||
: null),
|
||||
(page
|
||||
? m('.inside.vertical', [
|
||||
m('div.page-goback', ['« ', m(m.route.Link, {
|
||||
href: page.parent_path
|
||||
? '/page/' + page.parent_path
|
||||
: '/'
|
||||
}, page.parent_name || 'Home')]
|
||||
),
|
||||
m('h2', page.name)
|
||||
])
|
||||
: null),
|
||||
m('.inside', [
|
||||
this.children.length
|
||||
? m('aside', [
|
||||
m('h5', page ? 'View ' + page.name + ':' : 'Categories'),
|
||||
this.children.map((page) => {
|
||||
return [
|
||||
m(m.route.Link, { class: 'root', href: '/page/' + page.path }, page.name),
|
||||
(page.children && page.children.length
|
||||
? m('ul', page.children.map(function(subpage) {
|
||||
return m('li', m(m.route.Link, { class: 'child', href: '/page/' + subpage.path }, subpage.name))
|
||||
}))
|
||||
: null),
|
||||
]
|
||||
})
|
||||
])
|
||||
: null,
|
||||
m('div.container', [
|
||||
(page && page.pictureFallback
|
||||
? m('a.cover', {
|
||||
rel: 'noopener',
|
||||
href: page.media_path,
|
||||
}, [
|
||||
m('picture', [
|
||||
m('source', {
|
||||
srcset: page.pictureAvif,
|
||||
sizes: page.pictureCover,
|
||||
type: 'image/avif',
|
||||
}),
|
||||
m('img', {
|
||||
srcset: page.pictureJpeg,
|
||||
sizes: page.pictureCover,
|
||||
alt: 'Image for news item ' + page.name,
|
||||
src: page.pictureFallback,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
: null),
|
||||
(page && page.content
|
||||
? page.content.blocks.map(block => {
|
||||
return m(EditorBlock, { block: block })
|
||||
})
|
||||
: null),
|
||||
(page && this.data.articles.length
|
||||
? [
|
||||
m('h5', 'Latest posts under ' + page.name + ':'),
|
||||
this.data.articles.map(function(article) {
|
||||
return m(Articleslim, { article: article })
|
||||
}),
|
||||
]
|
||||
: null),
|
||||
(!page && this.data.articles.length
|
||||
? this.data.articles.map(function(article) {
|
||||
return m(Article, { article: article })
|
||||
})
|
||||
: null),
|
||||
m(Paginator, {
|
||||
base: page ? '/page/' + page.path : '/',
|
||||
page: this.currentPage,
|
||||
perPage: ArticlesPerPage,
|
||||
total: this.data.total_articles,
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = SitePage
|
|
@ -1,44 +0,0 @@
|
|||
const Pages = {
|
||||
oninit: function(vnode) {
|
||||
this.onbeforeupdate(vnode)
|
||||
},
|
||||
|
||||
onbeforeupdate: function(vnode) {
|
||||
this.total = vnode.attrs.total
|
||||
this.currentPage = vnode.attrs.page
|
||||
this.perPage = vnode.attrs.perPage || 10
|
||||
this.maxPage = this.total / this.perPage + 1
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
if (this.total <= this.perPage) return null
|
||||
return m('pages', [
|
||||
this.currentPage > 1
|
||||
? [
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base,
|
||||
}, 'First'),
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base + (this.currentPage > 2
|
||||
? '?page=' + (this.currentPage - 1)
|
||||
: ''
|
||||
),
|
||||
}, 'Previous'),
|
||||
]
|
||||
: m('div'),
|
||||
m('div', 'Page ' + this.currentPage),
|
||||
this.currentPage < this.maxPage
|
||||
? [
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base + '?page=' + (this.currentPage + 1),
|
||||
}, 'Next'),
|
||||
m(m.route.Link, {
|
||||
href: vnode.attrs.base + '?page=' + this.maxPage,
|
||||
}, 'Last')
|
||||
]
|
||||
: m('div'),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Pages
|
|
@ -9,8 +9,8 @@
|
|||
"scripts": {
|
||||
"start": "node --experimental-modules index.mjs",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build:prod": "asbundle app/index.js public/assets/app.js && asbundle app/admin.js public/assets/admin.js",
|
||||
"build": "asbundle app/index.js public/assets/app.js && asbundle app/admin.js public/assets/admin.js",
|
||||
"build:prod": "asbundle app/index.js public/assets/app.js && asbundle app/admin/admin.js public/assets/admin.js",
|
||||
"build": "asbundle app/index.js public/assets/app.js && asbundle app/admin/admin.js public/assets/admin.js",
|
||||
"dev:build": "npm-watch build",
|
||||
"dev:server": "node dev.mjs | bunyan",
|
||||
"dev": "npm-watch dev:server",
|
||||
|
|
|
@ -23,16 +23,13 @@
|
|||
</head>
|
||||
<body class="daymode">
|
||||
<script type="text/javascript" nonce="{{=nonce}}">
|
||||
if (localStorage.getItem('darkmode')) {document.body.className = 'darkmodeon';}
|
||||
if (localStorage.getItem('nfp_sites_darkmode')) {document.body.className = 'darkmode';}
|
||||
window.__nfptree = {{=payloadTree}};
|
||||
window.__nfpdata = {{=payloadData}};
|
||||
window.__nfplinks = {{=payloadLinks}};
|
||||
</script>
|
||||
<div class="maincontainer">
|
||||
<div id="nav"></div>
|
||||
<main id="main"></main>
|
||||
<footer id="footer"></footer>
|
||||
</div>
|
||||
<div id="header"></div>
|
||||
<main id="main"></main>
|
||||
<footer id="footer"></footer>
|
||||
<script type="text/javascript" src="/assets/app.js?v={{=version}}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue