diff --git a/.eslintrc b/.eslintrc index 1d23fea..f83589c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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, diff --git a/api/access/middleware.mjs b/api/access/middleware.mjs index 157d266..2f83eb2 100644 --- a/api/access/middleware.mjs +++ b/api/access/middleware.mjs @@ -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() + } } } diff --git a/api/article/model.mjs b/api/article/model.mjs index af89e8c..ff846cc 100644 --- a/api/article/model.mjs +++ b/api/article/model.mjs @@ -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'], }) }, diff --git a/api/jwt.mjs b/api/jwt.mjs index 4ea8a83..8ad2661 100644 --- a/api/jwt.mjs +++ b/api/jwt.mjs @@ -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, diff --git a/api/media/resize.mjs b/api/media/resize.mjs index 4e83f0c..adc59ea 100644 --- a/api/media/resize.mjs +++ b/api/media/resize.mjs @@ -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({ diff --git a/api/media/routes.mjs b/api/media/routes.mjs index 33929a7..162f1e8 100644 --- a/api/media/routes.mjs +++ b/api/media/routes.mjs @@ -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')) diff --git a/api/serve.mjs b/api/serve.mjs index c651bf4..ac314a6 100644 --- a/api/serve.mjs +++ b/api/serve.mjs @@ -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) } }) diff --git a/api/serveindex.mjs b/api/serveindex.mjs new file mode 100644 index 0000000..be57fb3 --- /dev/null +++ b/api/serveindex.mjs @@ -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') +} diff --git a/app/admin.js b/app/admin.js index 5286764..c6a1cba 100644 --- a/app/admin.js +++ b/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], +} diff --git a/app/admin/articles.js b/app/admin/articles.js index 3490392..f46c2d6 100644 --- a/app/admin/articles.js +++ b/app/admin/articles.js @@ -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, diff --git a/app/admin/editarticle.js b/app/admin/editarticle.js index 25174f2..22cfbda 100644 --- a/app/admin/editarticle.js +++ b/app/admin/editarticle.js @@ -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'), diff --git a/app/admin/editpage.js b/app/admin/editpage.js index f11725f..8b9c1b9 100644 --- a/app/admin/editpage.js +++ b/app/admin/editpage.js @@ -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) { diff --git a/app/admin/editstaff.js b/app/admin/editstaff.js index fc9c0a3..982906e 100644 --- a/app/admin/editstaff.js +++ b/app/admin/editstaff.js @@ -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' } }, diff --git a/app/admin/pages.js b/app/admin/pages.js index 4ba4455..87e9adf 100644 --- a/app/admin/pages.js +++ b/app/admin/pages.js @@ -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) diff --git a/app/admin/stafflist.js b/app/admin/stafflist.js index 1bd4c85..b266dee 100644 --- a/app/admin/stafflist.js +++ b/app/admin/stafflist.js @@ -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 diff --git a/app/api/media.js b/app/api/media.js index 30b9a44..b6a641b 100644 --- a/app/api/media.js +++ b/app/api/media.js @@ -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, }) } diff --git a/app/api/page.p.js b/app/api/page.p.js index d92db72..d3f4a67 100644 --- a/app/api/page.p.js +++ b/app/api/page.p.js @@ -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', }) } diff --git a/app/article/article.js b/app/article/article.js index 345f277..0779d9b 100644 --- a/app/article/article.js +++ b/app/article/article.js @@ -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 diff --git a/app/footer/footer.scss b/app/footer/footer.scss index 2f92f4d..c64903d 100644 --- a/app/footer/footer.scss +++ b/app/footer/footer.scss @@ -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; } } diff --git a/app/frontpage/frontpage.js b/app/frontpage/frontpage.js index 61bd0bd..6a94dbf 100644 --- a/app/frontpage/frontpage.js +++ b/app/frontpage/frontpage.js @@ -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] diff --git a/app/frontpage/frontpage.scss b/app/frontpage/frontpage.scss index 11e3251..19e18fb 100644 --- a/app/frontpage/frontpage.scss +++ b/app/frontpage/frontpage.scss @@ -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 { diff --git a/app/index.js b/app/index.js index 604c0c8..de49630 100644 --- a/app/index.js +++ b/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) diff --git a/app/login/logout.js b/app/login/logout.js index f825992..9a1a9be 100644 --- a/app/login/logout.js +++ b/app/login/logout.js @@ -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() { diff --git a/app/pages/page.js b/app/pages/page.js index 458d459..8a2b00e 100644 --- a/app/pages/page.js +++ b/app/pages/page.js @@ -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 diff --git a/app/widgets/fileupload.js b/app/widgets/fileupload.js index 3b396b9..21eccea 100644 --- a/app/widgets/fileupload.js +++ b/app/widgets/fileupload.js @@ -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) diff --git a/package.json b/package.json index de481a4..4f327e8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/assets/img/heart.jpg b/public/assets/img/heart.jpg new file mode 100644 index 0000000..cb6daf3 Binary files /dev/null and b/public/assets/img/heart.jpg differ diff --git a/public/assets/img/heart.xcf b/public/assets/img/heart.xcf new file mode 100644 index 0000000..4bdfb6e Binary files /dev/null and b/public/assets/img/heart.xcf differ diff --git a/public/index.html b/public/index.html index 92b521b..9704155 100644 --- a/public/index.html +++ b/public/index.html @@ -2,9 +2,20 @@
-