Finished initial release-worthy version, added appveyor and starting testing appveyor auto building
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
This commit is contained in:
parent
17882b457d
commit
71b1725655
199 changed files with 779 additions and 12287 deletions
|
@ -1,48 +0,0 @@
|
|||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: docker:latest
|
||||
environment:
|
||||
- di: "nfpis/nfpmoe"
|
||||
- dtag: "latest"
|
||||
- service_name: "nfpmoe"
|
||||
- target_port: "7030" # The public port
|
||||
- service_port: "4030" # Container port
|
||||
working_directory: ~/nfpmoe
|
||||
steps:
|
||||
- run:
|
||||
name: Update and install SSH & Git & sed
|
||||
command: apk update && apk upgrade && apk add --no-cache bash git openssh sed
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Replace version in config
|
||||
command: |
|
||||
sed -i "s/circleci_version_number/${CIRCLE_BUILD_NUM}/g" config/config.default.json
|
||||
- run:
|
||||
name: Build docker image
|
||||
command: docker build -t ${di}:build_${CIRCLE_BUILD_NUM} -t ${di}:${CIRCLE_SHA1} -t ${di}:${dtag} .
|
||||
- run:
|
||||
name: Push to docker
|
||||
command: |
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
docker push ${di} --all-tags
|
||||
- deploy:
|
||||
name: Deploy to production
|
||||
command: |
|
||||
if [ "${CIRCLE_BRANCH}" != "master" ]; then
|
||||
echo Not running on master. Exiting.
|
||||
exit 0
|
||||
fi
|
||||
echo "$MASTER_HOST" | base64 -d > ~/.ssh/master_host
|
||||
echo "$MASTER_KEY" | base64 -d > ~/.ssh/master_key
|
||||
chmod 600 ~/.ssh/master_key
|
||||
ssh -p 51120 -i ~/.ssh/master_key -o "UserKnownHostsFile ~/.ssh/master_host" root@212.30.212.2 "docker ${service_name} ${di}:build_${CIRCLE_BUILD_NUM} ${target_port} ${service_port}"
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_deploy:
|
||||
jobs:
|
||||
- build:
|
||||
context: org-global
|
1
.npmrc
1
.npmrc
|
@ -1 +0,0 @@
|
|||
package-lock=false
|
|
@ -1 +1 @@
|
|||
# nfp_moe
|
||||
# nfp_sites
|
82
appveyor.yml
Normal file
82
appveyor.yml
Normal file
|
@ -0,0 +1,82 @@
|
|||
# version format
|
||||
version: '{build}'
|
||||
deploy: on
|
||||
|
||||
# branches to build
|
||||
branches:
|
||||
# whitelist
|
||||
only:
|
||||
- master
|
||||
|
||||
# Do not build on tags (GitHub, Bitbucket, GitLab, Gitea)
|
||||
skip_tags: true
|
||||
|
||||
# Maximum number of concurrent jobs for the project
|
||||
max_jobs: 1
|
||||
clone_depth: 1
|
||||
|
||||
# Build worker image (VM template)
|
||||
build_cloud: Docker
|
||||
|
||||
environment:
|
||||
docker_image: node:16-alpine
|
||||
npm_config_cache: /appveyor/projects/cache
|
||||
|
||||
build_script:
|
||||
- sh: |
|
||||
chown -R node:node /appveyor/projects
|
||||
chmod -R 777 /appveyor/projects
|
||||
|
||||
apk add curl jq
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Finished installling curl and jq"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in *; do
|
||||
[ ! -d "$f" ] || [ -L "$f" ] || [ "$f" = "base" ] && continue;
|
||||
echo "checking $f";
|
||||
cd $f
|
||||
CURR_VER="$(cat package.json | jq -r .name)_v$(cat package.json | jq -r .version)"
|
||||
CURR_NAME="$(cat package.json | jq -r .name) v$(cat package.json | jq -r .version)"
|
||||
echo "Checking https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases for name ${CURR_NAME}"
|
||||
curl -s -X GET -H "Authorization: token $deploytoken" https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases | grep -o "\"name\"\:\"${CURR_NAME}\"" > /dev/null
|
||||
|
||||
if [ $? -eq 0 ] ; then
|
||||
echo "Skipping $f since $CURR_NAME already exists";
|
||||
else
|
||||
rm base
|
||||
cp -Rf ../base ./base
|
||||
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
./7zas a -xr!*.xcf -mx9 "${CURR_VER}_build-sc.7z" package.json index.mjs api base public
|
||||
echo "Creating release on gitea"
|
||||
RELEASE_RESULT=$(curl \
|
||||
-X POST \
|
||||
-H "Authorization: token $deploytoken" \
|
||||
-H "Content-Type: application/json" \
|
||||
https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases \
|
||||
-d "{\"tag_name\":\"v${CURR_VER}\",\"name\":\"v${CURR_NAME}\",\"body\":\"Automatic release from Appveyor from ${APPVEYOR_REPO_COMMIT} :\n\n${APPVEYOR_REPO_COMMIT_MESSAGE}\"}")
|
||||
RELEASE_ID=$(echo $RELEASE_RESULT | jq -r .id)
|
||||
echo "Adding ${CURR_VER}_build-sc.7z to release ${RELEASE_ID}"
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Authorization: token $deploytoken" \
|
||||
-F "attachment=@${CURR_VER}_build-sc.7z" \
|
||||
https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases/$RELEASE_ID/assets
|
||||
|
||||
echo "Deplying to production"
|
||||
MAN_PORT=$(cat package.json | jq -r .port)
|
||||
MAN_NAME=$(cat package.json | jq -r .name)
|
||||
Echo "curl -X POST http://192.168.93.50:$MAN_PORT/update/$MAN_NAME"
|
||||
curl -X POST http://192.168.93.50:$MAN_PORT/update/$MAN_NAME
|
||||
fi
|
||||
cd..
|
||||
done
|
||||
|
||||
# on build failure
|
||||
on_failure:
|
||||
- sh: echo on_failure
|
|
@ -1,5 +1,5 @@
|
|||
import { parseArticles, parseArticle } from './util.mjs'
|
||||
import { uploadMedia, uploadFile } from '../media/upload.mjs'
|
||||
import { uploadMedia, uploadFile, deleteFile } from '../media/upload.mjs'
|
||||
import { mediaToDatabase } from '../media/util.mjs'
|
||||
|
||||
export default class ArticleRoutes {
|
||||
|
@ -7,6 +7,7 @@ export default class ArticleRoutes {
|
|||
Object.assign(this, {
|
||||
uploadMedia: uploadMedia,
|
||||
uploadFile: uploadFile,
|
||||
deleteFile: deleteFile,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -69,7 +70,6 @@ export default class ArticleRoutes {
|
|||
params = params.concat(mediaToDatabase(banner, body.remove_banner === 'true'))
|
||||
params = params.concat(mediaToDatabase(media, body.remove_media === 'true'))
|
||||
}
|
||||
console.log(params)
|
||||
let res = await ctx.db.safeCallProc('article_auth_get_update_create', params)
|
||||
|
||||
ctx.body = this.private_getUpdateArticle_resOutput(res)
|
||||
|
@ -89,8 +89,6 @@ export default class ArticleRoutes {
|
|||
|
||||
/** PUT: /api/auth/articles/:id */
|
||||
async auth_updateCreateSingleArticle(ctx) {
|
||||
console.log(ctx.req.body)
|
||||
|
||||
let newBanner = null
|
||||
let newMedia = null
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { HttpError } from 'flaska'
|
|||
import { decode, encode } from '../util.mjs'
|
||||
import config from '../config.mjs'
|
||||
|
||||
const levels = {
|
||||
export const RankLevels = {
|
||||
Normal: 1,
|
||||
Manager: 10,
|
||||
Admin: 100,
|
||||
|
@ -12,7 +12,48 @@ const levels = {
|
|||
const issuer = config.get('mssql:connectionUser')
|
||||
const secret = config.get('jwtsecret')
|
||||
|
||||
export function authenticate(minLevel = levels.Manager) {
|
||||
export function verifyValidToken(parts, minLevel) {
|
||||
if (parts.length !== 4) {
|
||||
throw new HttpError(401, 'Authentication token invalid')
|
||||
}
|
||||
|
||||
const hmac = crypto.createHmac('sha256', secret)
|
||||
hmac.update([parts[0], parts[1], parts[2]].join('.'))
|
||||
let apiSignature = encode(hmac.digest())
|
||||
|
||||
if (apiSignature !== parts[3]) {
|
||||
throw new HttpError(401, 'Authentication token invalid signature')
|
||||
}
|
||||
|
||||
let header
|
||||
let body
|
||||
try {
|
||||
header = JSON.parse(decode(parts[0]).toString('utf8'))
|
||||
body = JSON.parse(decode(parts[1]).toString('utf8'))
|
||||
} catch (err) {
|
||||
throw new HttpError(401, 'Authentication token invalid json')
|
||||
}
|
||||
|
||||
if (header.alg !== 'HS256') {
|
||||
throw new HttpError(401, 'Authentication token invalid alg')
|
||||
}
|
||||
|
||||
let unixNow = Math.floor(Date.now() / 1000)
|
||||
|
||||
// Validate token, add a little skew support for issued_at
|
||||
if (body.iss !== issuer || !body.iat || !body.exp
|
||||
|| body.iat > unixNow + 300 || body.exp <= unixNow) {
|
||||
throw new HttpError(403, 'Authentication token expired or invalid')
|
||||
}
|
||||
|
||||
if (body.rank < minLevel) {
|
||||
throw new HttpError(401, 'User does not have access to this resource')
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
export function authenticate(minLevel = RankLevels.Manager) {
|
||||
return function(ctx) {
|
||||
if (!ctx.req.headers.authorization) {
|
||||
throw new HttpError(401, 'Authentication token missing')
|
||||
|
@ -23,40 +64,7 @@ export function authenticate(minLevel = levels.Manager) {
|
|||
|
||||
let parts = ctx.req.headers.authorization.slice(7).split('.')
|
||||
|
||||
if (parts.length !== 4) {
|
||||
throw new HttpError(401, 'Authentication token invalid')
|
||||
}
|
||||
|
||||
const hmac = crypto.createHmac('sha256', secret)
|
||||
const token = [parts[0], parts[1], parts[2]].join('.')
|
||||
hmac.update(token)
|
||||
let apiSignature = encode(hmac.digest())
|
||||
|
||||
if (apiSignature !== parts[3]) {
|
||||
throw new HttpError(401, 'Authentication token invalid signature')
|
||||
}
|
||||
|
||||
let header
|
||||
let body
|
||||
try {
|
||||
header = JSON.parse(decode(parts[0]).toString('utf8'))
|
||||
body = JSON.parse(decode(parts[1]).toString('utf8'))
|
||||
} catch (err) {
|
||||
throw new HttpError(401, 'Authentication token invalid json')
|
||||
}
|
||||
|
||||
if (header.alg !== 'HS256') {
|
||||
throw new HttpError(401, 'Authentication token invalid alg')
|
||||
}
|
||||
|
||||
let unixNow = Math.floor(Date.now() / 1000)
|
||||
|
||||
// Validate token, add a little skew support for issued_at
|
||||
if (body.iss !== issuer || !body.iat || !body.exp
|
||||
|| body.iat > unixNow + 300 || body.exp <= unixNow) {
|
||||
throw new HttpError(403, 'Authentication token expired or invalid')
|
||||
}
|
||||
ctx.state.auth_user = body
|
||||
ctx.state.auth_token = token
|
||||
ctx.state.auth_user = verifyValidToken(parts, minLevel)
|
||||
ctx.state.auth_token = [parts[0], parts[1], parts[2]].join('.')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'lodash'
|
||||
import nconf from 'nconf-lite'
|
||||
import Nconf from 'nconf-lite'
|
||||
|
||||
const nconf = new Nconf()
|
||||
|
||||
// Helper method for global usage.
|
||||
nconf.inTest = () => nconf.get('NODE_ENV') === 'test'
|
||||
|
@ -16,30 +17,15 @@ nconf.inTest = () => nconf.get('NODE_ENV') === 'test'
|
|||
nconf.env({
|
||||
separator: '__',
|
||||
whitelist: [
|
||||
'DATABASE_URL',
|
||||
'NODE_ENV',
|
||||
'server__port',
|
||||
'server__host',
|
||||
'knex__connection__host',
|
||||
'knex__connection__user',
|
||||
'knex__connection__database',
|
||||
'knex__connection__password',
|
||||
'knex__connectionslave__host',
|
||||
'knex__connectionslave__user',
|
||||
'knex__connectionslave__database',
|
||||
'knex__connectionslave__password',
|
||||
'upload__baseurl',
|
||||
'upload__port',
|
||||
'upload__host',
|
||||
'upload__name',
|
||||
'upload__secret',
|
||||
'bunyan__name',
|
||||
'mssql__connectionString',
|
||||
'media__secret',
|
||||
'media__iss',
|
||||
'media__path',
|
||||
'media__filePath',
|
||||
'media__removePath',
|
||||
'frontend__url',
|
||||
'jwt__secret',
|
||||
'sessionsecret',
|
||||
'bcrypt',
|
||||
'name',
|
||||
'NODE_VERSION',
|
||||
'jwtsecret',
|
||||
],
|
||||
parseValues: true,
|
||||
})
|
||||
|
@ -67,6 +53,7 @@ nconf.defaults({
|
|||
"iss": "dev",
|
||||
"path": "https://media.nfp.is/media/resize",
|
||||
"filePath": "https://media.nfp.is/media",
|
||||
"removePath": "https://media.nfp.is/media/",
|
||||
"preview": {
|
||||
"out": "base64",
|
||||
"format": "avif",
|
||||
|
@ -171,14 +158,7 @@ nconf.defaults({
|
|||
}
|
||||
},
|
||||
},
|
||||
"fileSize": 524288000,
|
||||
"upload": {
|
||||
"baseurl": "https://cdn.nfp.is",
|
||||
"port": "2111",
|
||||
"host": "storage01.nfp.is",
|
||||
"name": "nfpmoe-dev",
|
||||
"secret": "nfpmoe-dev"
|
||||
}
|
||||
"fileSize": 524288000
|
||||
})
|
||||
|
||||
|
||||
|
|
25
base/db.mjs
25
base/db.mjs
|
@ -10,31 +10,6 @@ export function initPool(core, config) {
|
|||
core.log.info('MSSQL connection open')
|
||||
})
|
||||
|
||||
let waiting = false
|
||||
|
||||
/*pool.on('error', function(error) {
|
||||
if (error.length) {
|
||||
let msg = 'Error in MSSQL pool\n => ' + error[0].message.trim()
|
||||
for (let i = 1; i < error.length; i++) {
|
||||
msg += '\n => ' + error[i].message.trim()
|
||||
}
|
||||
core.log.error(msg)
|
||||
} else {
|
||||
core.log.error('Error in MSSQL pool')
|
||||
core.log.error(error)
|
||||
}
|
||||
|
||||
if (waiting) { return }
|
||||
core.log.warn('Attempting to connect again in 5 seconds')
|
||||
waiting = true
|
||||
setTimeout(function() {
|
||||
waiting = false
|
||||
console.log('opening')
|
||||
pool.open()
|
||||
console.log('done')
|
||||
}, 5000)
|
||||
})*/
|
||||
|
||||
core.log.info('Attempting to connect to MSSQL server')
|
||||
pool.open()
|
||||
|
||||
|
|
|
@ -48,10 +48,8 @@ export default class Client {
|
|||
reject(err)
|
||||
})
|
||||
req.on('timeout', function() {
|
||||
console.log("req.on('timeout')")
|
||||
req.destroy()
|
||||
let d2 = new Date()
|
||||
console.log((d2 - d1))
|
||||
reject(new Error(`Request ${method} ${path} timed out`))
|
||||
})
|
||||
req.on('response', res => {
|
||||
|
@ -141,6 +139,16 @@ export default class Client {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
delete(url, body) {
|
||||
let parsed = JSON.stringify(body)
|
||||
return this.customRequest('DELETE', url, parsed, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': parsed.length,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
upload(url, files, method = 'POST', body = {}) {
|
||||
const boundary = `---------${this.random(32)}`
|
||||
|
|
|
@ -71,4 +71,13 @@ export function uploadFile(file) {
|
|||
type: file.type,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteFile(filename) {
|
||||
const media = config.get('media')
|
||||
|
||||
const client = new Client()
|
||||
let token = client.createJwt({ iss: media.iss }, media.secret)
|
||||
|
||||
return client.delete(media.removePath + filename + '?token=' + token, { })
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
"flaska": "^1.3.1",
|
||||
"formidable": "^1.2.6",
|
||||
"msnodesqlv8": "^2.4.7",
|
||||
"nconf-lite": "^1.0.1"
|
||||
"nconf-lite": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { parsePage, parsePagesToTree } from './util.mjs'
|
||||
import { parsePage, parsePages, parsePagesToTree } from './util.mjs'
|
||||
import { uploadMedia, uploadFile } from '../media/upload.mjs'
|
||||
import { parseArticle, parseArticles } from '../article/util.mjs'
|
||||
import { mediaToDatabase } from '../media/util.mjs'
|
||||
|
@ -36,13 +36,17 @@ export default class PageRoutes {
|
|||
}
|
||||
|
||||
/** GET: /api/pages/[path] */
|
||||
async getPage(ctx) {
|
||||
async getPage(ctx, onlyReturn = false) {
|
||||
let res = await ctx.db.safeCallProc('pages_get_single', [
|
||||
ctx.params.path || null,
|
||||
Math.max(ctx.query.get('page') || 1, 1),
|
||||
Math.min(ctx.query.get('per_page') || 10, 25),
|
||||
])
|
||||
|
||||
if (onlyReturn) {
|
||||
return this.getPage_resOut(res)
|
||||
}
|
||||
|
||||
ctx.body = this.getPage_resOut(res)
|
||||
}
|
||||
|
||||
|
@ -61,7 +65,7 @@ export default class PageRoutes {
|
|||
ctx.state.auth_token
|
||||
])
|
||||
|
||||
ctx.body = parsePagesToTree(res.first)
|
||||
ctx.body = parsePagesToTree(parsePages(res.first))
|
||||
}
|
||||
|
||||
async private_getUpdatePage(ctx, body = null, banner = null, media = null) {
|
||||
|
@ -80,7 +84,6 @@ export default class PageRoutes {
|
|||
params = params.concat(mediaToDatabase(banner, body.remove_banner === 'true'))
|
||||
params = params.concat(mediaToDatabase(media, body.remove_media === 'true'))
|
||||
}
|
||||
console.log(params)
|
||||
let res = await ctx.db.safeCallProc('pages_auth_get_update_create', params)
|
||||
|
||||
let out = {
|
||||
|
@ -98,8 +101,6 @@ export default class PageRoutes {
|
|||
|
||||
/** PUT: /api/auth/pages/:id */
|
||||
async auth_updateCreateSinglePage(ctx) {
|
||||
console.log(ctx.req.body)
|
||||
|
||||
let newBanner = null
|
||||
let newMedia = null
|
||||
|
||||
|
|
|
@ -24,6 +24,13 @@ export function parsePagesToTree(pages) {
|
|||
}
|
||||
}
|
||||
|
||||
export function parsePages(pages) {
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
parsePage(pages[i])
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
export function parsePage(page) {
|
||||
if (!page) {
|
||||
return null
|
||||
|
|
|
@ -3,21 +3,26 @@ import dot from 'dot'
|
|||
import { FileResponse, HttpError } from 'flaska'
|
||||
import fs from 'fs/promises'
|
||||
import fsSync from 'fs'
|
||||
import { RankLevels, verifyValidToken } from './authentication/security.mjs'
|
||||
|
||||
export default class ServeHandler {
|
||||
constructor(opts = {}) {
|
||||
Object.assign(this, {
|
||||
pageRoutes: opts.pageRoutes,
|
||||
fs: opts.fs || fs,
|
||||
fsSync: opts.fsSync || fsSync,
|
||||
root: opts.root,
|
||||
template: null,
|
||||
frontend: opts.frontend || 'http://localhost:4000',
|
||||
version: opts.version || 'version',
|
||||
})
|
||||
Object.assign(this, opts)
|
||||
if (!opts.fs) {
|
||||
this.fs = fs
|
||||
}
|
||||
if (!opts.fsSync) {
|
||||
this.fsSync = fsSync
|
||||
}
|
||||
if (!opts.frontend) {
|
||||
this.frontend = 'http://localhost:4000'
|
||||
}
|
||||
if (!opts.version) {
|
||||
this.version = 'version'
|
||||
}
|
||||
|
||||
let indexFile = fsSync.readFileSync(path.join(this.root, 'index.html'))
|
||||
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadLinks', 'payloadTree', 'version', 'nonce'] })
|
||||
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce'] })
|
||||
// console.log(indexFile.toString())
|
||||
}
|
||||
|
||||
|
@ -28,23 +33,35 @@ export default class ServeHandler {
|
|||
/** GET: /::file */
|
||||
serve(ctx) {
|
||||
if (ctx.params.file.startsWith('api/')) {
|
||||
throw new HttpError(404, 'Not Found: ' + ctx.params.file, { status: 404, message: 'Not Found: ' + ctx.params.file })
|
||||
return this.serveIndex(ctx)
|
||||
}
|
||||
|
||||
let file = path.resolve(path.join(this.root, ctx.params.file ? ctx.params.file : 'index.html'))
|
||||
|
||||
if (!ctx.params.file || ctx.params.file === 'index.html') {
|
||||
if (!ctx.params.file
|
||||
|| ctx.params.file === 'index.html'
|
||||
|| ctx.params.file.startsWith('/page')
|
||||
|| ctx.params.file.startsWith('/article')
|
||||
|| ctx.params.file.startsWith('/admin')) {
|
||||
return this.serveIndex(ctx)
|
||||
}
|
||||
|
||||
if (!file.startsWith(this.root)) {
|
||||
ctx.status = 404
|
||||
ctx.body = 'HTTP 404 Error'
|
||||
return
|
||||
return this.serveIndex(ctx)
|
||||
}
|
||||
|
||||
if (file.indexOf('admin') >= 0
|
||||
&& (file.indexOf('.js') >= 0 || file.indexOf('.css') >= 0)) {
|
||||
verifyValidToken((ctx.query.get('token') || '').split('.'), RankLevels.Manager)
|
||||
}
|
||||
|
||||
return this.fs.stat(file)
|
||||
.then(function(stat) {
|
||||
if (file.indexOf('admin') === -1) {
|
||||
ctx.headers['Cache-Control'] = 'max-age=2592000'
|
||||
} else {
|
||||
ctx.headers['Cache-Control'] = 'no-store'
|
||||
}
|
||||
ctx.body = new FileResponse(file, stat)
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -56,25 +73,7 @@ export default class ServeHandler {
|
|||
}
|
||||
|
||||
async serveIndex(ctx) {
|
||||
let payload = {
|
||||
headerDescription: 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.',
|
||||
headerImage: this.frontend + '/assets/img/heart.png',
|
||||
headerTitle: 'NFP Moe - Anime/Manga translation group',
|
||||
headerUrl: this.frontend + ctx.url,
|
||||
payloadData: null,
|
||||
payloadLinks: null,
|
||||
payloadTree: null,
|
||||
version: this.version,
|
||||
nonce: ctx.state.nonce,
|
||||
}
|
||||
|
||||
try {
|
||||
payload.payloadTree = JSON.stringify(await this.pageRoutes.getPageTree(ctx, true))
|
||||
} catch (e) {
|
||||
ctx.log.error(e)
|
||||
}
|
||||
|
||||
ctx.body = this.template(payload)
|
||||
ctx.body = this.template({})
|
||||
ctx.type = 'text/html; charset=utf-8'
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,6 @@ export default class ArticleRoutes extends Parent {
|
|||
}
|
||||
}
|
||||
let file = await this.uploadFile(ctx.req.files.file)
|
||||
console.log(file)
|
||||
|
||||
let params = [
|
||||
ctx.state.auth_token,
|
||||
|
@ -79,10 +78,10 @@ export default class ArticleRoutes extends Parent {
|
|||
null,
|
||||
null,
|
||||
ctx.params.fileId,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
|
||||
let res = await ctx.db.safeCallProc('article_auth_file_create_delete', params)
|
||||
console.log(res)
|
||||
await this.deleteFile(res.first[0].deleted_filename)
|
||||
}
|
||||
}
|
34
nfp_moe/api/serve.mjs
Normal file
34
nfp_moe/api/serve.mjs
Normal file
|
@ -0,0 +1,34 @@
|
|||
|
||||
import Parent from '../base/serve.mjs'
|
||||
|
||||
export default class ServeHandler extends Parent {
|
||||
async serveIndex(ctx) {
|
||||
let payload = {
|
||||
headerDescription: 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.',
|
||||
headerImage: this.frontend + '/assets/img/heart.png',
|
||||
headerTitle: 'NFP Moe - Anime/Manga translation group',
|
||||
headerUrl: this.frontend + ctx.url,
|
||||
payloadData: null,
|
||||
payloadTree: null,
|
||||
version: this.version,
|
||||
nonce: ctx.state.nonce,
|
||||
}
|
||||
|
||||
try {
|
||||
payload.payloadTree = JSON.stringify(await this.pageRoutes.getPageTree(ctx, true))
|
||||
if (ctx.url === '/' || (ctx.url.startsWith('/page/') && ctx.url.lastIndexOf('/') === 5)) {
|
||||
ctx.params.path = null
|
||||
if (ctx.url.lastIndexOf('/') === 5) {
|
||||
ctx.params.path = ctx.url.slice(ctx.url.lastIndexOf('/') + 1)
|
||||
}
|
||||
payload.payloadData = JSON.stringify(await this.pageRoutes.getPage(ctx, true))
|
||||
}
|
||||
console.log('url', ctx.url)
|
||||
} catch (e) {
|
||||
ctx.log.error(e)
|
||||
}
|
||||
|
||||
ctx.body = this.template(payload)
|
||||
ctx.type = 'text/html; charset=utf-8'
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import config from '../base/config.mjs'
|
||||
import Parent from '../base/server.mjs'
|
||||
import ServeHandler from '../base/serve.mjs'
|
||||
import ServeHandler from './serve.mjs'
|
||||
import ArticleRoutes from './article_routes.mjs'
|
||||
import PageRoutes from './page_routes.mjs'
|
||||
|
||||
|
@ -14,6 +14,7 @@ export default class Server extends Parent {
|
|||
this.routes.page = new PageRoutes()
|
||||
this.routes.serve = new ServeHandler({
|
||||
pageRoutes: this.routes.page,
|
||||
articleRoutes: this.routes.article,
|
||||
root: localUtil.getPathFromRoot('../public'),
|
||||
version: this.core.app.running,
|
||||
frontend: config.get('frontend:url'),
|
||||
|
|
|
@ -24,6 +24,17 @@ const FileUpload = {
|
|||
vnode.attrs.onfile(out)
|
||||
},
|
||||
|
||||
fileRemoved: function(vnode) {
|
||||
if (this.preview) {
|
||||
this.preview.clear()
|
||||
this.preview = null
|
||||
vnode.attrs.onfile(null)
|
||||
}
|
||||
if (vnode.attrs.media) {
|
||||
vnode.attrs.ondelete(vnode.attrs.media)
|
||||
}
|
||||
},
|
||||
|
||||
oninit: function(vnode) {
|
||||
this.loading = false
|
||||
this.preview = null
|
||||
|
@ -66,10 +77,10 @@ const FileUpload = {
|
|||
type: 'file',
|
||||
onchange: this.fileChanged.bind(this, vnode),
|
||||
}),
|
||||
/*imageLink && vnode.attrs.ondelete
|
||||
? m('button.remove', { onclick: vnode.attrs.ondelete })
|
||||
imageLink && vnode.attrs.ondelete
|
||||
? m('button.remove', { onclick: this.fileRemoved.bind(this, vnode), title: 'Remove image' }, m.trust('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M144 400C144 408.8 136.8 416 128 416C119.2 416 112 408.8 112 400V176C112 167.2 119.2 160 128 160C136.8 160 144 167.2 144 176V400zM240 400C240 408.8 232.8 416 224 416C215.2 416 208 408.8 208 400V176C208 167.2 215.2 160 224 160C232.8 160 240 167.2 240 176V400zM336 400C336 408.8 328.8 416 320 416C311.2 416 304 408.8 304 400V176C304 167.2 311.2 160 320 160C328.8 160 336 167.2 336 176V400zM310.1 22.56L336.9 64H432C440.8 64 448 71.16 448 80C448 88.84 440.8 96 432 96H416V432C416 476.2 380.2 512 336 512H112C67.82 512 32 476.2 32 432V96H16C7.164 96 0 88.84 0 80C0 71.16 7.164 64 16 64H111.1L137 22.56C145.8 8.526 161.2 0 177.7 0H270.3C286.8 0 302.2 8.526 310.1 22.56V22.56zM148.9 64H299.1L283.8 39.52C280.9 34.84 275.8 32 270.3 32H177.7C172.2 32 167.1 34.84 164.2 39.52L148.9 64zM64 432C64 458.5 85.49 480 112 480H336C362.5 480 384 458.5 384 432V96H64V432z"/></svg>'))
|
||||
: null,
|
||||
this.loading
|
||||
/*this.loading
|
||||
? m('div.loading-spinner')
|
||||
: null,*/
|
||||
])
|
||||
|
|
|
@ -108,6 +108,12 @@ const AdminArticles = {
|
|||
? 'rowfeatured'
|
||||
: ''
|
||||
}, [
|
||||
m('td.nopadding', article.banner_alt_prefix
|
||||
? m('a', { href: article.banner_path, target: '_blank' }, m('img', { src: article.banner_alt_prefix + '_small.avif' }))
|
||||
: m.trust(' ') ),
|
||||
m('td.nopadding', article.media_alt_prefix
|
||||
? m('a', { href: article.media_path, target: '_blank' }, m('img', { src: article.media_alt_prefix + '_small.avif' }))
|
||||
: m.trust(' ') ),
|
||||
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)),
|
||||
|
@ -138,6 +144,8 @@ const AdminArticles = {
|
|||
: m('table', [
|
||||
m('thead',
|
||||
m('tr', [
|
||||
m('th', 'Banner'),
|
||||
m('th', 'Cover'),
|
||||
m('th', 'Title'),
|
||||
m('th', 'Path'),
|
||||
m('th', 'Page'),
|
||||
|
|
|
@ -19,6 +19,8 @@ const EditArticle = {
|
|||
this.pages = [{id: null, name: 'Frontpage'}]
|
||||
this.pages = this.pages.concat(PageTree.getFlatTree())
|
||||
|
||||
this.removeBanner = false
|
||||
this.removeMedia = false
|
||||
this.newBanner = null
|
||||
this.newMedia = null
|
||||
this.dateInstance = null
|
||||
|
@ -93,7 +95,13 @@ const EditArticle = {
|
|||
if (name === 'path') {
|
||||
this.editedPath = true
|
||||
} else if (name === 'name' && !this.editedPath) {
|
||||
this.data.article.path = this.data.article.name.toLowerCase().replace(/ /g, '-')
|
||||
this.data.article.path = this.data.article.name
|
||||
.normalize("NFD").replace(/[\u0300-\u036f]/g, '')
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '-')
|
||||
.replace(/\-{2,}/g, '-')
|
||||
.replace(/^-+/, '')
|
||||
.replace(/-+$/, '')
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -108,14 +116,20 @@ const EditArticle = {
|
|||
mediaUploaded: function(type, file) {
|
||||
if (type === 'banner') {
|
||||
this.newBanner = file
|
||||
this.removeBanner = false
|
||||
} else {
|
||||
this.newMedia = file
|
||||
this.removeMedia = false
|
||||
}
|
||||
},
|
||||
|
||||
mediaRemoved: function(type) {
|
||||
this.data.article['remove_' + type] = true
|
||||
this.data.article[type + '_prefix'] = null
|
||||
this.data.article[type + '_alt_prefix'] = null
|
||||
if (type === 'banner') {
|
||||
this.removeBanner = true
|
||||
} else {
|
||||
this.removeMedia = true
|
||||
}
|
||||
},
|
||||
|
||||
save: function(vnode, e) {
|
||||
|
@ -146,8 +160,8 @@ const EditArticle = {
|
|||
formData.append('path', this.data.article.path)
|
||||
formData.append('page_id', this.data.article.page_id || null)
|
||||
formData.append('publish_at', this.dateInstance.inputElem.value.replace(', ', 'T') + 'Z')
|
||||
formData.append('remove_banner', this.data.article.remove_banner ? true : false)
|
||||
formData.append('remove_media', this.data.article.remove_media ? true : false)
|
||||
formData.append('remove_banner', this.removeBanner ? true : false)
|
||||
formData.append('remove_media', this.removeMedia ? true : false)
|
||||
|
||||
this.loading = true
|
||||
|
||||
|
@ -192,6 +206,9 @@ const EditArticle = {
|
|||
},
|
||||
|
||||
refreshFiles: function(vnode, prom) {
|
||||
this.loading = true
|
||||
m.redraw()
|
||||
|
||||
prom.then(() => {
|
||||
return api.sendRequest({
|
||||
method: 'GET',
|
||||
|
@ -204,21 +221,16 @@ const EditArticle = {
|
|||
this.error = err.message
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
confirmRemoveFile: function(vnode, file) {
|
||||
console.log(file)
|
||||
/*Dialogue.showDialogue(
|
||||
'Delete file',
|
||||
'Are you sure you want to remove "' + file.filename + '"',
|
||||
'Delete',
|
||||
'alert',
|
||||
'Don\'t delete',
|
||||
'',
|
||||
page,
|
||||
this.confirmRemovePage.bind(this, vnode))*/
|
||||
return this.refreshFiles(vnode, api.sendRequest({
|
||||
method: 'DELETE',
|
||||
url: '/api/auth/articles/' + this.lastid + '/files/' + file.id,
|
||||
}))
|
||||
},
|
||||
|
||||
askConfirmRemoveFile: function(vnode, file) {
|
||||
|
|
|
@ -66,6 +66,12 @@ const AdminPages = {
|
|||
drawPage: function(vnode, page) {
|
||||
return [
|
||||
m('tr', [
|
||||
m('td.nopadding', page.banner_alt_prefix
|
||||
? m('a', { href: page.banner_path, target: '_blank' }, m('img', { src: page.banner_alt_prefix + '_small.avif' }))
|
||||
: m.trust(' ') ),
|
||||
m('td.nopadding', page.media_alt_prefix
|
||||
? m('a', { href: page.media_path, target: '_blank' }, m('img', { src: page.media_alt_prefix + '_small.avif' }))
|
||||
: m.trust(' ') ),
|
||||
m('td', [
|
||||
page.parent_id ? m('span.subpage', ' - ') : null,
|
||||
m(m.route.Link, { href: '/admin/pages/' + page.id }, page.name),
|
||||
|
@ -97,6 +103,8 @@ const AdminPages = {
|
|||
: m('table', [
|
||||
m('thead',
|
||||
m('tr', [
|
||||
m('th', 'Banner'),
|
||||
m('th', 'Cover'),
|
||||
m('th', 'Title'),
|
||||
m('th', 'Path'),
|
||||
m('th.right', 'Updated'),
|
||||
|
|
|
@ -14,8 +14,8 @@ const Article = {
|
|||
if (this.lastId !== article.id) {
|
||||
this.lastId = article.id
|
||||
|
||||
let pictureCover = '(max-width: 639px) calc(100vw - 40px), '
|
||||
+ '(max-width: 1000px) 300px, '
|
||||
let pictureCover = '(max-width: 639px) calc(100vw - 10px), '
|
||||
+ '(max-width: 1000px) calc(100vw - 265px), '
|
||||
+ '400px'
|
||||
if (vnode.attrs.full) {
|
||||
pictureCover = '(max-width: 1280) calc(100vw - 2rem), '
|
||||
|
|
|
@ -9,29 +9,33 @@ const Footer = {
|
|||
|
||||
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)',
|
||||
m('div.first'),
|
||||
m('div.middle', [
|
||||
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)',
|
||||
]),
|
||||
]),
|
||||
m('div.asuna.spritesheet'),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ const Menu = {
|
|||
} else {
|
||||
localStorage.removeItem(DarkModeStorageName)
|
||||
}
|
||||
document.body.className = (this.darkIsOn ? 'darkmode ' : 'daymode ')
|
||||
document.body.className = (this.darkIsOn ? 'nightmode ' : 'daymode ')
|
||||
+ (window.supportsavif ? 'avifsupport' : 'jpegonly')
|
||||
},
|
||||
|
||||
|
@ -63,8 +63,11 @@ const Menu = {
|
|||
m('header', [
|
||||
m('div.inside', [
|
||||
m(m.route.Link,
|
||||
{ href: '/', class: 'logo' },
|
||||
m('h1', 'NFP Moe')
|
||||
{ href: '/', class: 'title' },
|
||||
[
|
||||
m('div.logo.spritesheet'),
|
||||
m('h1', 'NFP Moe'),
|
||||
]
|
||||
),
|
||||
m('aside', [
|
||||
Authentication.currentUser
|
||||
|
@ -77,7 +80,7 @@ const Menu = {
|
|||
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'),
|
||||
// m(m.route.Link, { hidden: Authentication.currentUser.rank < 100, href: '/admin/staff' }, 'Staff'),
|
||||
])
|
||||
]
|
||||
: null,
|
||||
|
|
|
@ -6,6 +6,7 @@ const Footer = require('./footer')
|
|||
const Login = require('./site_login')
|
||||
const SitePage = require('./site_page')
|
||||
const SiteArticle = require('./site_article')
|
||||
const NotFoundView = require('./site_404')
|
||||
window.m = m
|
||||
|
||||
m.route.setOrig = m.route.set
|
||||
|
@ -29,6 +30,7 @@ const allRoutes = {
|
|||
'/article/:id': SiteArticle,
|
||||
'/admin/:path': AdminResolver,
|
||||
'/admin/:path/:id': AdminResolver,
|
||||
'/:404...': NotFoundView,
|
||||
}
|
||||
|
||||
// Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called.
|
||||
|
|
|
@ -20,7 +20,7 @@ const Paginator = {
|
|||
),
|
||||
}, 'Previous'),
|
||||
]
|
||||
: m('div'),
|
||||
: [ m('div'), m('div')],
|
||||
m('div', 'Page ' + currentPage),
|
||||
currentPage < maxPage
|
||||
? [
|
||||
|
@ -31,7 +31,7 @@ const Paginator = {
|
|||
href: vnode.attrs.base + '?page=' + maxPage,
|
||||
}, 'Last')
|
||||
]
|
||||
: m('div'),
|
||||
: [ m('div'), m('div')],
|
||||
])
|
||||
},
|
||||
}
|
||||
|
|
20
nfp_moe/app/site_404.js
Normal file
20
nfp_moe/app/site_404.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const m = require('mithril')
|
||||
|
||||
const NotFoundView = {
|
||||
oninit: function(vnode) {
|
||||
},
|
||||
|
||||
oncreate: function() {
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m('div.wrapper.not_found', [
|
||||
m('h4.notfound', '404: Page, article or file was not found'),
|
||||
m('div.asuna.spritesheet'),
|
||||
]),
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = NotFoundView
|
|
@ -2,6 +2,7 @@ const m = require('mithril')
|
|||
const Article = require('./article')
|
||||
const api = require('./api')
|
||||
const media = require('./media')
|
||||
const NotFoundView = require('./site_404')
|
||||
|
||||
window.LoadComments = false
|
||||
window.HyvorLoaded = false
|
||||
|
@ -22,6 +23,7 @@ const SiteArticle = {
|
|||
this.path = m.route.param('id')
|
||||
this.data.article = window.__nfpdata
|
||||
window.__nfpdata = null
|
||||
this.afterData()
|
||||
} else {
|
||||
this.fetchArticle(vnode)
|
||||
}
|
||||
|
@ -58,10 +60,7 @@ const SiteArticle = {
|
|||
})
|
||||
.then((result) => {
|
||||
this.data = result
|
||||
|
||||
if (!this.data.article) {
|
||||
this.error = 'Article not found'
|
||||
}
|
||||
this.afterData()
|
||||
}, (err) => {
|
||||
this.error = err.message
|
||||
})
|
||||
|
@ -73,6 +72,12 @@ const SiteArticle = {
|
|||
})
|
||||
},
|
||||
|
||||
afterData: function() {
|
||||
if (!this.data.article) {
|
||||
this.error = 'Article not found'
|
||||
}
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
let article = this.data.article
|
||||
|
||||
|
@ -80,13 +85,16 @@ const SiteArticle = {
|
|||
this.loading
|
||||
? m('div.loading-spinner')
|
||||
: null,
|
||||
!this.loading && this.error
|
||||
!this.loading && this.error === 'Article not found'
|
||||
? NotFoundView.view()
|
||||
: null,
|
||||
!this.loading && this.error && this.error !== 'Article not found'
|
||||
? m('div.wrapper', m('div.error', {
|
||||
onclick: () => {
|
||||
this.error = ''
|
||||
this.fetchPage(vnode)
|
||||
},
|
||||
}, 'Page error: ' + this.error + '. Click here to try again'))
|
||||
}, 'Article error: ' + this.error + '. Click here to try again'))
|
||||
: null,
|
||||
(article
|
||||
? m('.inside.vertical', [
|
||||
|
|
|
@ -59,13 +59,14 @@ const Login = {
|
|||
return [
|
||||
m('div.wrapper', [
|
||||
this.loading ? m('div.loading-spinner') : null,
|
||||
m('div.login--first'),
|
||||
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('label', 'Email or name'),
|
||||
m('input', {
|
||||
type: 'text',
|
||||
value: this.username,
|
||||
|
@ -82,6 +83,7 @@ const Login = {
|
|||
value: 'Log in',
|
||||
}),
|
||||
]),
|
||||
m('div.login--asuna.spritesheet'),
|
||||
]),
|
||||
]
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ const Article = require('./article')
|
|||
const Articleslim = require('./article_slim')
|
||||
const media = require('./media')
|
||||
const EditorBlock = require('./editorblock')
|
||||
const NotFoundView = require('./site_404')
|
||||
|
||||
const ArticlesPerPage = 10
|
||||
|
||||
|
@ -25,12 +26,20 @@ const SitePage = {
|
|||
this.children = []
|
||||
this.currentPage = Number(m.route.param('page')) || 1
|
||||
|
||||
console.log('test', window.__nfpdata)
|
||||
if (window.__nfpdata) {
|
||||
this.path = m.route.param('id')
|
||||
this.data = window.__nfpdata
|
||||
this.lastpage = this.currentPage
|
||||
|
||||
this.data = window.__nfpdata
|
||||
window.__nfpdata = null
|
||||
window.__nfpsubdata = null
|
||||
|
||||
this.children = PageTree.Tree
|
||||
if (this.path) {
|
||||
this.children = PageTree.TreeMap.get(this.path)
|
||||
this.children = this.children && this.children.children || []
|
||||
}
|
||||
this.afterData()
|
||||
} else {
|
||||
this.fetchPage(vnode)
|
||||
}
|
||||
|
@ -76,28 +85,7 @@ const SitePage = {
|
|||
})
|
||||
.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'
|
||||
}
|
||||
|
||||
this.picture = media.generatePictureSource(this.data.page,
|
||||
'(max-width: 840px) calc(100vw - 82px), '
|
||||
+ '758px')
|
||||
|
||||
if (this.lastpage !== 1) {
|
||||
document.title = 'Page ' + this.lastpage + ' - ' + title
|
||||
} else {
|
||||
document.title = title
|
||||
}
|
||||
this.afterData()
|
||||
}, (err) => {
|
||||
this.error = err.message
|
||||
})
|
||||
|
@ -109,6 +97,30 @@ const SitePage = {
|
|||
})
|
||||
},
|
||||
|
||||
afterData: function() {
|
||||
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'
|
||||
}
|
||||
|
||||
this.picture = media.generatePictureSource(this.data.page,
|
||||
'(max-width: 840px) calc(100vw - 82px), '
|
||||
+ '758px')
|
||||
|
||||
if (this.lastpage !== 1) {
|
||||
document.title = 'Page ' + this.lastpage + ' - ' + title
|
||||
} else {
|
||||
document.title = title
|
||||
}
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
let page = this.data.page
|
||||
let featuredBanner = media.getBannerImage(this.data.featured, '/article/')
|
||||
|
@ -118,7 +130,10 @@ const SitePage = {
|
|||
this.loading
|
||||
? m('div.loading-spinner')
|
||||
: null,
|
||||
!this.loading && this.error
|
||||
!this.loading && this.error === 'Page not found'
|
||||
? NotFoundView.view()
|
||||
: null,
|
||||
!this.loading && this.error && this.error !== 'Page not found'
|
||||
? m('div.wrapper', m('div.error', {
|
||||
onclick: () => {
|
||||
this.error = ''
|
||||
|