dev
This commit is contained in:
parent
cbd38bc212
commit
e9c7cdfb7a
29 changed files with 802 additions and 352 deletions
|
@ -1,13 +1,12 @@
|
||||||
import { FormidableHandler } from 'flaska'
|
|
||||||
import { parseFiles } from '../file/util.mjs'
|
|
||||||
import { parseArticles, parseArticle } from './util.mjs'
|
import { parseArticles, parseArticle } from './util.mjs'
|
||||||
import { upload } from '../media/upload.mjs'
|
import { uploadMedia, uploadFile } from '../media/upload.mjs'
|
||||||
import { mediaToDatabase } from '../media/util.mjs'
|
import { mediaToDatabase } from '../media/util.mjs'
|
||||||
|
|
||||||
export default class ArticleRoutes {
|
export default class ArticleRoutes {
|
||||||
constructor(opts = {}) {
|
constructor(opts = {}) {
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
upload: upload,
|
uploadMedia: uploadMedia,
|
||||||
|
uploadFile: uploadFile,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +25,13 @@ export default class ArticleRoutes {
|
||||||
async getArticle(ctx) {
|
async getArticle(ctx) {
|
||||||
let res = await ctx.db.safeCallProc('article_get_single', [ctx.params.path])
|
let res = await ctx.db.safeCallProc('article_get_single', [ctx.params.path])
|
||||||
|
|
||||||
let out = {
|
ctx.body = this.getArticle_resOutput(res)
|
||||||
article: parseArticle(res.results[0][0]),
|
|
||||||
files: parseFiles(res.results[1]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = out
|
getArticle_resOutput(res) {
|
||||||
|
return {
|
||||||
|
article: parseArticle(res.results[0][0]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET: /api/auth/articles */
|
/** GET: /api/auth/articles */
|
||||||
|
@ -72,15 +72,15 @@ export default class ArticleRoutes {
|
||||||
console.log(params)
|
console.log(params)
|
||||||
let res = await ctx.db.safeCallProc('article_auth_get_update_create', params)
|
let res = await ctx.db.safeCallProc('article_auth_get_update_create', params)
|
||||||
|
|
||||||
let out = {
|
ctx.body = this.private_getUpdateArticle_resOutput(res)
|
||||||
article: parseArticle(res.results[0][0]) || { publish_at: new Date() },
|
|
||||||
files: parseFiles(res.results[1]),
|
|
||||||
staff: res.results[2],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = out
|
private_getUpdateArticle_resOutput(res) {
|
||||||
|
return {
|
||||||
|
article: parseArticle(res.results[0][0] || {}),
|
||||||
|
staff: res.results[1],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** GET: /api/auth/articles/:id */
|
/** GET: /api/auth/articles/:id */
|
||||||
auth_getSingleArticle(ctx) {
|
auth_getSingleArticle(ctx) {
|
||||||
|
@ -98,13 +98,13 @@ export default class ArticleRoutes {
|
||||||
|
|
||||||
if (ctx.req.files.banner) {
|
if (ctx.req.files.banner) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.upload(ctx.req.files.banner)
|
this.uploadMedia(ctx.req.files.banner)
|
||||||
.then(res => { newBanner = res })
|
.then(res => { newBanner = res })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (ctx.req.files.media) {
|
if (ctx.req.files.media) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.upload(ctx.req.files.media)
|
this.uploadMedia(ctx.req.files.media)
|
||||||
.then(res => { newMedia = res })
|
.then(res => { newMedia = res })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { parseFile } from '../file/util.mjs'
|
|
||||||
import { contentToBlocks, parseMediaAndBanner } from '../util.mjs'
|
import { contentToBlocks, parseMediaAndBanner } from '../util.mjs'
|
||||||
|
|
||||||
export function parseArticles(articles) {
|
export function parseArticles(articles) {
|
||||||
|
@ -8,18 +7,6 @@ export function parseArticles(articles) {
|
||||||
return articles
|
return articles
|
||||||
}
|
}
|
||||||
|
|
||||||
export function combineFilesWithArticles(articles, files) {
|
|
||||||
let articleMap = new Map()
|
|
||||||
|
|
||||||
articles.forEach(article => {
|
|
||||||
article.files = []
|
|
||||||
articleMap.set(article.id, article)
|
|
||||||
})
|
|
||||||
files.forEach(file => {
|
|
||||||
articleMap.get(file.id).files.push(parseFile(file))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseArticle(article) {
|
export function parseArticle(article) {
|
||||||
if (!article) {
|
if (!article) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -66,6 +66,7 @@ nconf.defaults({
|
||||||
"secret": "upload-secret-key-here",
|
"secret": "upload-secret-key-here",
|
||||||
"iss": "dev",
|
"iss": "dev",
|
||||||
"path": "https://media.nfp.is/media/resize",
|
"path": "https://media.nfp.is/media/resize",
|
||||||
|
"filePath": "https://media.nfp.is/media",
|
||||||
"preview": {
|
"preview": {
|
||||||
"out": "base64",
|
"out": "base64",
|
||||||
"format": "avif",
|
"format": "avif",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import config from '../config.mjs'
|
import config from '../config.mjs'
|
||||||
import Client from './client.mjs'
|
import Client from './client.mjs'
|
||||||
|
|
||||||
export function upload(file) {
|
export function uploadMedia(file) {
|
||||||
const media = config.get('media')
|
const media = config.get('media')
|
||||||
|
|
||||||
const client = new Client()
|
const client = new Client()
|
||||||
|
@ -53,3 +53,22 @@ export function upload(file) {
|
||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uploadFile(file) {
|
||||||
|
const media = config.get('media')
|
||||||
|
|
||||||
|
const client = new Client()
|
||||||
|
let token = client.createJwt({ iss: media.iss }, media.secret)
|
||||||
|
|
||||||
|
return client.upload(media.filePath + '?token=' + token, { file: {
|
||||||
|
file: file.path,
|
||||||
|
filename: file.name,
|
||||||
|
} }, 'POST').then(res => {
|
||||||
|
return {
|
||||||
|
filename: res.filename,
|
||||||
|
path: res.path,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dot": "^2.0.0-beta.1",
|
"dot": "^2.0.0-beta.1",
|
||||||
"flaska": "^1.3.0",
|
"flaska": "^1.3.1",
|
||||||
"formidable": "^1.2.6",
|
"formidable": "^1.2.6",
|
||||||
"msnodesqlv8": "^2.4.7",
|
"msnodesqlv8": "^2.4.7",
|
||||||
"nconf-lite": "^1.0.1"
|
"nconf-lite": "^1.0.1"
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { parsePage, parsePagesToTree } from './util.mjs'
|
import { parsePage, parsePagesToTree } from './util.mjs'
|
||||||
import { upload } from '../media/upload.mjs'
|
import { uploadMedia, uploadFile } from '../media/upload.mjs'
|
||||||
import { combineFilesWithArticles, parseArticle, parseArticles } from '../article/util.mjs'
|
import { parseArticle, parseArticles } from '../article/util.mjs'
|
||||||
import { mediaToDatabase } from '../media/util.mjs'
|
import { mediaToDatabase } from '../media/util.mjs'
|
||||||
|
|
||||||
|
|
||||||
export default class PageRoutes {
|
export default class PageRoutes {
|
||||||
constructor(opts = {}) {
|
constructor(opts = {}) {
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
upload: upload,
|
uploadMedia: uploadMedia,
|
||||||
|
uploadFile: uploadFile,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,16 +43,16 @@ export default class PageRoutes {
|
||||||
Math.min(ctx.query.get('per_page') || 10, 25),
|
Math.min(ctx.query.get('per_page') || 10, 25),
|
||||||
])
|
])
|
||||||
|
|
||||||
let out = {
|
ctx.body = this.getPage_resOut(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPage_resOut(res) {
|
||||||
|
return {
|
||||||
page: parsePage(res.results[0][0]),
|
page: parsePage(res.results[0][0]),
|
||||||
articles: parseArticles(res.results[1]),
|
articles: parseArticles(res.results[1]),
|
||||||
total_articles: res.results[2][0].total_articles,
|
total_articles: res.results[2][0].total_articles,
|
||||||
featured: parseArticle(res.results[4][0]),
|
featured: parseArticle(res.results[4][0]),
|
||||||
}
|
}
|
||||||
|
|
||||||
combineFilesWithArticles(out.articles, res.results[3])
|
|
||||||
|
|
||||||
ctx.body = out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET: /api/auth/pages */
|
/** GET: /api/auth/pages */
|
||||||
|
@ -106,13 +107,13 @@ export default class PageRoutes {
|
||||||
|
|
||||||
if (ctx.req.files.banner) {
|
if (ctx.req.files.banner) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.upload(ctx.req.files.banner)
|
this.uploadMedia(ctx.req.files.banner)
|
||||||
.then(res => { newBanner = res })
|
.then(res => { newBanner = res })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (ctx.req.files.media) {
|
if (ctx.req.files.media) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.upload(ctx.req.files.media)
|
this.uploadMedia(ctx.req.files.media)
|
||||||
.then(res => { newMedia = res })
|
.then(res => { newMedia = res })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,29 +26,17 @@ export default class Server {
|
||||||
this.authenticate = authenticate
|
this.authenticate = authenticate
|
||||||
this.formidable = FormidableHandler.bind(this, formidable)
|
this.formidable = FormidableHandler.bind(this, formidable)
|
||||||
this.jsonHandler = JsonHandler
|
this.jsonHandler = JsonHandler
|
||||||
this.routes = [
|
this.routes = {
|
||||||
new PageRoutes(),
|
page: new PageRoutes(),
|
||||||
new ArticleRoutes(),
|
article: new ArticleRoutes(),
|
||||||
new AuthenticationRoutes(),
|
auth: new AuthenticationRoutes(),
|
||||||
]
|
}
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
init() { }
|
init() { }
|
||||||
|
|
||||||
getRouteInstance(type) {
|
|
||||||
for (let route of this.routes) {
|
|
||||||
if (route instanceof type) {
|
|
||||||
return route
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addCustomRoutes() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
runCreateServer() {
|
runCreateServer() {
|
||||||
// Create our server
|
// Create our server
|
||||||
this.flaska = new Flaska(this.flaskaOptions, this.http)
|
this.flaska = new Flaska(this.flaskaOptions, this.http)
|
||||||
|
@ -89,8 +77,9 @@ export default class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
runRegisterRoutes() {
|
runRegisterRoutes() {
|
||||||
for (let route of this.routes) {
|
let keys = Object.keys(this.routes)
|
||||||
route.register(this)
|
for (let key of keys) {
|
||||||
|
this.routes[key].register(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +94,6 @@ export default class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
this.addCustomRoutes()
|
|
||||||
this.runCreateServer()
|
this.runCreateServer()
|
||||||
this.runRegisterRoutes()
|
this.runRegisterRoutes()
|
||||||
|
|
||||||
|
|
84
nfp_moe/api/article_routes.mjs
Normal file
84
nfp_moe/api/article_routes.mjs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { parseArticles, parseArticle } from '../base/article/util.mjs'
|
||||||
|
import { parseFiles } from './file/util.mjs'
|
||||||
|
import Parent from '../base/article/routes.mjs'
|
||||||
|
import { decodeTorrentFile } from './file/torrent.mjs'
|
||||||
|
|
||||||
|
export default class ArticleRoutes extends Parent {
|
||||||
|
register(server) {
|
||||||
|
super.register(server)
|
||||||
|
server.flaska.post('/api/auth/articles/:id/files', [
|
||||||
|
server.authenticate(),
|
||||||
|
server.formidable({ maxFileSize: 100 * 1024 * 1024, }),
|
||||||
|
], this.auth_addFileToArticle.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
getArticle_resOutput(res) {
|
||||||
|
return {
|
||||||
|
article: parseArticle(res.results[0][0]),
|
||||||
|
files: parseFiles(res.results[1]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private_getUpdateArticle_resOutput(res) {
|
||||||
|
return {
|
||||||
|
article: parseArticle(res.results[0][0] || {}),
|
||||||
|
files: parseFiles(res.results[1]),
|
||||||
|
staff: res.results[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POST: /api/auth/articles/:id/files */
|
||||||
|
async auth_addFileToArticle(ctx) {
|
||||||
|
if (!ctx.req.files.file) {
|
||||||
|
throw new HttpError(422, 'Missing file in upload')
|
||||||
|
}
|
||||||
|
|
||||||
|
let meta = {}
|
||||||
|
if (ctx.req.files.file.name.endsWith('.torrent')) {
|
||||||
|
try {
|
||||||
|
let torrent = await decodeTorrentFile(ctx.req.files.file.path)
|
||||||
|
meta.torrent = {
|
||||||
|
name: torrent.name,
|
||||||
|
announce: torrent.announce,
|
||||||
|
hash: torrent.infoHash,
|
||||||
|
files: torrent.files.map(file => ({ name: file.name, size: file.length })),
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.log.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let file = await this.uploadFile(ctx.req.files.file)
|
||||||
|
|
||||||
|
let params = [
|
||||||
|
ctx.state.auth_token,
|
||||||
|
ctx.params.id,
|
||||||
|
file.filename,
|
||||||
|
file.type,
|
||||||
|
file.path,
|
||||||
|
file.size,
|
||||||
|
JSON.stringify(meta),
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
]
|
||||||
|
|
||||||
|
await ctx.db.safeCallProc('article_auth_file_create_delete', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DELETE: /api/auth/articles/:id/files/:fileId */
|
||||||
|
async auth_addFileToArticle(ctx) {
|
||||||
|
let params = [
|
||||||
|
ctx.state.auth_token,
|
||||||
|
ctx.params.id,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
ctx.params.fileId,
|
||||||
|
0,
|
||||||
|
]
|
||||||
|
|
||||||
|
let res = await ctx.db.safeCallProc('article_auth_file_create_delete', params)
|
||||||
|
console.log(res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import path from 'path'
|
||||||
import bencode from 'bencode'
|
import bencode from 'bencode'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -9,10 +12,9 @@ Taken from parse-torrent
|
||||||
* @param {Buffer|Object} torrent
|
* @param {Buffer|Object} torrent
|
||||||
* @return {Object} parsed torrent
|
* @return {Object} parsed torrent
|
||||||
*/
|
*/
|
||||||
export function decodeTorrentFile (torrent) {
|
export async function decodeTorrentFile (file) {
|
||||||
if (Buffer.isBuffer(torrent)) {
|
let buffer = await fs.readFile(file)
|
||||||
torrent = bencode.decode(torrent)
|
let torrent = bencode.decode(buffer)
|
||||||
}
|
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
ensure(torrent.info, 'info')
|
ensure(torrent.info, 'info')
|
||||||
|
@ -36,7 +38,9 @@ export function decodeTorrentFile (torrent) {
|
||||||
announce: []
|
announce: []
|
||||||
}
|
}
|
||||||
|
|
||||||
result.infoHash = sha1.sync(result.infoBuffer)
|
result.infoHash = crypto.createHash('sha1')
|
||||||
|
.update(result.infoBuffer)
|
||||||
|
.digest('hex')
|
||||||
result.infoHashBuffer = Buffer.from(result.infoHash, 'hex')
|
result.infoHashBuffer = Buffer.from(result.infoHash, 'hex')
|
||||||
|
|
||||||
if (torrent.info.private !== undefined) result.private = !!torrent.info.private
|
if (torrent.info.private !== undefined) result.private = !!torrent.info.private
|
||||||
|
@ -103,3 +107,7 @@ function splitPieces (buf) {
|
||||||
function ensure (bool, fieldName) {
|
function ensure (bool, fieldName) {
|
||||||
if (!bool) throw new Error(`Torrent is missing required field: ${fieldName}`)
|
if (!bool) throw new Error(`Torrent is missing required field: ${fieldName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sumLength (sum, file) {
|
||||||
|
return sum + file.length
|
||||||
|
}
|
|
@ -5,6 +5,18 @@ export function parseFiles(files) {
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function combineFilesWithArticles(articles, files) {
|
||||||
|
let articleMap = new Map()
|
||||||
|
|
||||||
|
articles.forEach(article => {
|
||||||
|
article.files = []
|
||||||
|
articleMap.set(article.id, article)
|
||||||
|
})
|
||||||
|
files.forEach(file => {
|
||||||
|
articleMap.get(file.id).files.push(parseFile(file))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function parseFile(file) {
|
export function parseFile(file) {
|
||||||
file.url = 'https://cdn.nfp.is' + file.path
|
file.url = 'https://cdn.nfp.is' + file.path
|
||||||
file.magnet = null
|
file.magnet = null
|
19
nfp_moe/api/page_routes.mjs
Normal file
19
nfp_moe/api/page_routes.mjs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { parseArticles, parseArticle } from '../../base/article/util.mjs'
|
||||||
|
import Parent from '../../base/page/routes.mjs'
|
||||||
|
import { parsePage } from '../../base/page/util.mjs'
|
||||||
|
import { combineFilesWithArticles } from './file/util.mjs'
|
||||||
|
|
||||||
|
export default class PageRoutes extends Parent {
|
||||||
|
getPage_resOut(res) {
|
||||||
|
let out = {
|
||||||
|
page: parsePage(res.results[0][0]),
|
||||||
|
articles: parseArticles(res.results[1]),
|
||||||
|
total_articles: res.results[2][0].total_articles,
|
||||||
|
featured: parseArticle(res.results[4][0]),
|
||||||
|
}
|
||||||
|
|
||||||
|
combineFilesWithArticles(out.articles, res.results[3])
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
import config from '../base/config.mjs'
|
import config from '../base/config.mjs'
|
||||||
import Parent from '../base/server.mjs'
|
import Parent from '../base/server.mjs'
|
||||||
import ServeHandler from '../base/serve.mjs'
|
import ServeHandler from '../base/serve.mjs'
|
||||||
import PageRoutes from '../base/page/routes.mjs'
|
import ArticleRoutes from './article_routes.mjs'
|
||||||
|
import PageRoutes from './page_routes.mjs'
|
||||||
|
|
||||||
export default class Server extends Parent {
|
export default class Server extends Parent {
|
||||||
init() {
|
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'; frame-src talk.hyvor.com` //; frame-ancestors 'none'`
|
super.init()
|
||||||
}
|
|
||||||
|
|
||||||
addCustomRoutes() {
|
|
||||||
let page = this.getRouteInstance(PageRoutes)
|
|
||||||
|
|
||||||
let localUtil = new this.core.sc.Util(import.meta.url)
|
let localUtil = new this.core.sc.Util(import.meta.url)
|
||||||
this.routes.push(new ServeHandler({
|
|
||||||
pageRoutes: page,
|
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'; frame-src talk.hyvor.com` //; frame-ancestors 'none'`
|
||||||
|
this.routes.article = new ArticleRoutes()
|
||||||
|
this.routes.page = new PageRoutes()
|
||||||
|
this.routes.serve = new ServeHandler({
|
||||||
|
pageRoutes: this.routes.page,
|
||||||
root: localUtil.getPathFromRoot('../public'),
|
root: localUtil.getPathFromRoot('../public'),
|
||||||
version: this.core.app.running,
|
version: this.core.app.running,
|
||||||
frontend: config.get('frontend:url'),
|
frontend: config.get('frontend:url'),
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const EditPage = require('./site_editpage')
|
const EditPage = require('./site_editpage')
|
||||||
const AllPages = require('./site_pages')
|
const AllPages = require('./site_pages')
|
||||||
const AllArticles = require('./site_articles')
|
const AllArticles = require('./site_articles')
|
||||||
const EditArticle = require('./editarticle')
|
const EditArticle = require('./site_editarticle')
|
||||||
const AllStaff = require('./stafflist')
|
const AllStaff = require('./stafflist')
|
||||||
const EditStaff = require('./editstaff')
|
const EditStaff = require('./editstaff')
|
||||||
const Dialogue = require('./dialogue')
|
const Dialogue = require('./dialogue')
|
||||||
|
|
|
@ -29,7 +29,9 @@ const Dialogue = {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
let data = Dialogue.showDialogueData
|
let data = Dialogue.showDialogueData
|
||||||
return data
|
return data
|
||||||
? m('div.floating-container.main', m('dialogue', [
|
? m('div.floating-container.main', {
|
||||||
|
onclick: this.onclose.bind(this),
|
||||||
|
}, m('dialogue', { onclick: function(e) { e.stopPropagation() } }, [
|
||||||
m('h2.title', data.title),
|
m('h2.title', data.title),
|
||||||
m('p', data.message),
|
m('p', data.message),
|
||||||
m('div.buttons', [
|
m('div.buttons', [
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
const Froala = {
|
|
||||||
files: [
|
|
||||||
{ type: 'css', url: 'https://cdn.jsdelivr.net/npm/froala-editor@3.0.4/css/froala_editor.pkgd.min.css' },
|
|
||||||
{ type: 'css', url: 'https://cdn.jsdelivr.net/npm/froala-editor@3.0.4/css/themes/gray.min.css' },
|
|
||||||
{ type: 'js', url: 'https://cdn.jsdelivr.net/npm/froala-editor@3.0.4/js/froala_editor.pkgd.min.js' },
|
|
||||||
],
|
|
||||||
loadedFiles: 0,
|
|
||||||
loadedFroala: false,
|
|
||||||
|
|
||||||
checkLoadedAll: function(res) {
|
|
||||||
if (Froala.loadedFiles < Froala.files.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Froala.loadedFroala = true
|
|
||||||
res()
|
|
||||||
},
|
|
||||||
|
|
||||||
createFroalaScript: function() {
|
|
||||||
if (Froala.loadedFroala) return Promise.resolve()
|
|
||||||
return new Promise(function(res) {
|
|
||||||
let onload = function() {
|
|
||||||
Froala.loadedFiles++
|
|
||||||
Froala.checkLoadedAll(res)
|
|
||||||
}
|
|
||||||
let head = document.getElementsByTagName('head')[0]
|
|
||||||
|
|
||||||
for (var i = 0; i < Froala.files.length; i++) {
|
|
||||||
let element
|
|
||||||
if (Froala.files[i].type === 'css') {
|
|
||||||
element = document.createElement('link')
|
|
||||||
element.setAttribute('rel', 'stylesheet')
|
|
||||||
element.setAttribute('type', 'text/css')
|
|
||||||
element.setAttribute('href', Froala.files[i].url)
|
|
||||||
} else {
|
|
||||||
element = document.createElement('script')
|
|
||||||
element.setAttribute('type', 'text/javascript')
|
|
||||||
element.setAttribute('src', Froala.files[i].url)
|
|
||||||
}
|
|
||||||
element.onload = onload
|
|
||||||
head.insertBefore(element, head.firstChild)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Froala
|
|
|
@ -13,7 +13,6 @@ const AdminArticles = {
|
||||||
articles: [],
|
articles: [],
|
||||||
total_articles: 0,
|
total_articles: 0,
|
||||||
}
|
}
|
||||||
this.removeArticle = null
|
|
||||||
this.currentPage = Number(m.route.param('page')) || 1
|
this.currentPage = Number(m.route.param('page')) || 1
|
||||||
|
|
||||||
this.fetchArticles(vnode)
|
this.fetchArticles(vnode)
|
||||||
|
@ -70,24 +69,34 @@ const AdminArticles = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmRemoveArticle: function(vnode) {
|
confirmRemoveArticle: function(vnode, article) {
|
||||||
let removingArticle = this.removeArticle
|
|
||||||
this.removeArticle = null
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
m.redraw()
|
m.redraw()
|
||||||
|
|
||||||
return common.sendRequest({
|
return api.sendRequest({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: '/api/auth/articles/' + removingArticle.id,
|
url: '/api/auth/articles/' + article.id,
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
() => this.fetchArticles(vnode),
|
() => this.fetchArticles(vnode),
|
||||||
(err) => {
|
(err) => { this.error = err.message }
|
||||||
this.error = err.message
|
)
|
||||||
|
.then(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
m.redraw()
|
m.redraw()
|
||||||
}
|
})
|
||||||
)
|
},
|
||||||
|
|
||||||
|
askConfirmRemovePage: function(vnode, article) {
|
||||||
|
Dialogue.showDialogue(
|
||||||
|
'Delete ' + article.name,
|
||||||
|
'Are you sure you want to remove "' + article.name + '" (' + article.path + ')',
|
||||||
|
'Remove',
|
||||||
|
'alert',
|
||||||
|
'Don\'t remove',
|
||||||
|
'',
|
||||||
|
article,
|
||||||
|
this.confirmRemoveArticle.bind(this, vnode))
|
||||||
},
|
},
|
||||||
|
|
||||||
drawArticle: function(vnode, article) {
|
drawArticle: function(vnode, article) {
|
||||||
|
@ -104,7 +113,7 @@ const AdminArticles = {
|
||||||
m('td', m(m.route.Link, { href: article.page_path }, article.page_name)),
|
m('td', m(m.route.Link, { href: article.page_path }, article.page_name)),
|
||||||
m('td.right', article.publish_at.replace('T', ' ').split('.')[0]),
|
m('td.right', article.publish_at.replace('T', ' ').split('.')[0]),
|
||||||
m('td.right', article.admin_name),
|
m('td.right', article.admin_name),
|
||||||
m('td.right', m('button', { onclick: function() { vnode.state.removeArticle = article } }, 'Remove')),
|
m('td.right', m('button', { onclick: this.askConfirmRemovePage.bind(this, vnode, article) }, 'Remove')),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -113,12 +122,13 @@ const AdminArticles = {
|
||||||
return [
|
return [
|
||||||
m('div.admin', [
|
m('div.admin', [
|
||||||
m('div.inside.vertical', [
|
m('div.inside.vertical', [
|
||||||
m('div.spacer'),
|
|
||||||
m('h2.title', 'All articles'),
|
|
||||||
m('div.actions', [
|
m('div.actions', [
|
||||||
|
m('div.filler'),
|
||||||
m('span', 'Actions:'),
|
m('span', 'Actions:'),
|
||||||
m(m.route.Link, { href: '/admin/articles/add' }, 'Create new article'),
|
m(m.route.Link, { href: '/admin/articles/add' }, 'Create new article'),
|
||||||
]),
|
]),
|
||||||
|
m('h2.title', 'All articles'),
|
||||||
|
m('div.container', [
|
||||||
m('div.error', {
|
m('div.error', {
|
||||||
hidden: !this.error,
|
hidden: !this.error,
|
||||||
onclick: function() { vnode.state.error = '' },
|
onclick: function() { vnode.state.error = '' },
|
||||||
|
@ -147,17 +157,7 @@ const AdminArticles = {
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
m(Dialogue, {
|
]),
|
||||||
hidden: vnode.state.removeArticle === null,
|
|
||||||
title: 'Delete ' + (vnode.state.removeArticle ? vnode.state.removeArticle.name : ''),
|
|
||||||
message: 'Are you sure you want to remove "' + (vnode.state.removeArticle ? vnode.state.removeArticle.name : '') + '" (' + (vnode.state.removeArticle ? vnode.state.removeArticle.path : '') + ')',
|
|
||||||
yes: 'Remove',
|
|
||||||
yesclass: 'alert',
|
|
||||||
no: 'Cancel',
|
|
||||||
noclass: 'cancel',
|
|
||||||
onyes: this.confirmRemoveArticle.bind(this, vnode),
|
|
||||||
onno: function() { vnode.state.removeArticle = null },
|
|
||||||
}),
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ const EditArticle = {
|
||||||
}
|
}
|
||||||
this.pages = [{id: null, name: 'Frontpage'}]
|
this.pages = [{id: null, name: 'Frontpage'}]
|
||||||
this.pages = this.pages.concat(PageTree.getFlatTree())
|
this.pages = this.pages.concat(PageTree.getFlatTree())
|
||||||
|
|
||||||
this.newBanner = null
|
this.newBanner = null
|
||||||
this.newMedia = null
|
this.newMedia = null
|
||||||
this.dateInstance = null
|
this.dateInstance = null
|
||||||
|
@ -62,12 +63,13 @@ const EditArticle = {
|
||||||
data
|
data
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.data = result
|
this.data = result
|
||||||
this.data.article.publish_at = new Date(this.data.article.publish_at)
|
|
||||||
|
|
||||||
if (this.data.article.id) {
|
if (this.data.article.id) {
|
||||||
|
this.data.article.publish_at = new Date(this.data.article.publish_at)
|
||||||
document.title = 'Editing: ' + this.data.article.name + ' - Admin NFP Moe'
|
document.title = 'Editing: ' + this.data.article.name + ' - Admin NFP Moe'
|
||||||
this.editedPath = true
|
this.editedPath = true
|
||||||
} else {
|
} else {
|
||||||
|
this.data.article.publish_at = new Date('3000-01-01')
|
||||||
document.title = 'Create Article - Admin NFP Moe'
|
document.title = 'Create Article - Admin NFP Moe'
|
||||||
}
|
}
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
@ -172,41 +174,249 @@ const EditArticle = {
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadFile: function(vnode, e) {
|
uploadFile: function(vnode, e) {
|
||||||
|
if (!e.target.files[0]) return
|
||||||
|
if (this.lastid === 'add') return
|
||||||
|
|
||||||
|
let file = e.target.files[0]
|
||||||
|
e.target.value = null
|
||||||
|
|
||||||
|
let formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
|
||||||
|
return this.refreshFiles(api.sendRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/auth/articles/' + this.lastid + '/files',
|
||||||
|
body: formData,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshFiles: function(vnode, prom) {
|
||||||
|
prom.then(() => {
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/auth/articles/' + this.lastid,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.data.files = result.files
|
||||||
|
}, (err) => {
|
||||||
|
this.error = err.message
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
m.redraw()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
askConfirmRemoveFile: function(vnode, file) {
|
||||||
|
console.log(file)
|
||||||
|
/*Dialogue.showDialogue(
|
||||||
|
'Delete ' + page.name,
|
||||||
|
'Are you sure you want to remove "' + page.name + '" (' + page.path + ')',
|
||||||
|
'Remove',
|
||||||
|
'alert',
|
||||||
|
'Don\'t remove',
|
||||||
|
'',
|
||||||
|
page,
|
||||||
|
this.confirmRemovePage.bind(this, vnode))*/
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
const showPublish = this.data.article
|
let article = this.data.article
|
||||||
? this.data.article.publish_at > new Date()
|
const showPublish = article
|
||||||
|
? article.publish_at > new Date()
|
||||||
: false
|
: false
|
||||||
const bannerImage = this.data.article && this.data.article.banner_prefix
|
console.log(!!article, article && article.publish_at > new Date(),'=', showPublish)
|
||||||
? this.data.article.banner_prefix + '_large.avif'
|
const bannerImage = article && article.banner_alt_prefix
|
||||||
|
? article.banner_alt_prefix + '_large.avif'
|
||||||
: null
|
: null
|
||||||
const mediaImage = this.data.article && this.data.article.media_prefix
|
const mediaImage = article && article.media_alt_prefix
|
||||||
? this.data.article.media_prefix + '_large.avif'
|
? article.media_alt_prefix + '_large.avif'
|
||||||
: null
|
: null
|
||||||
|
|
||||||
return [
|
return [
|
||||||
this.loading && !this.data.article
|
m('div.admin', [
|
||||||
|
!this.loading
|
||||||
|
? m(FileUpload, {
|
||||||
|
class: 'banner',
|
||||||
|
height: 150,
|
||||||
|
onfile: this.mediaUploaded.bind(this, 'banner'),
|
||||||
|
ondelete: this.mediaRemoved.bind(this, 'banner'),
|
||||||
|
media: bannerImage,
|
||||||
|
}, 'Click to upload banner image (only visible when featured)')
|
||||||
|
: null,
|
||||||
|
m('div.inside.vertical', [
|
||||||
|
m('div.actions', [
|
||||||
|
'« ',
|
||||||
|
m(m.route.Link, { href: '/admin/articles' }, 'Articles'),
|
||||||
|
article && article.id
|
||||||
|
? [
|
||||||
|
m('div.filler'),
|
||||||
|
m('span', 'Actions:'),
|
||||||
|
m(m.route.Link, { href: '/article/' + article.path }, 'View article'),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
|
m('h2.title', this.lastid === 'add' ? 'Create article' : 'Edit ' + (article && article.name || '(untitled)')),
|
||||||
|
m('div.container', [
|
||||||
|
m('div.error', {
|
||||||
|
hidden: !this.error,
|
||||||
|
onclick: function() { vnode.state.error = '' },
|
||||||
|
}, this.error),
|
||||||
|
this.loading
|
||||||
|
? m('div.loading-spinner')
|
||||||
|
: null,
|
||||||
|
article
|
||||||
|
? [
|
||||||
|
m(FileUpload, {
|
||||||
|
class: 'cover',
|
||||||
|
useimg: true,
|
||||||
|
onfile: this.mediaUploaded.bind(this, 'media'),
|
||||||
|
ondelete: this.mediaRemoved.bind(this, 'media'),
|
||||||
|
media: mediaImage,
|
||||||
|
}, 'Click to upload article image'),
|
||||||
|
m('form', {
|
||||||
|
onsubmit: this.save.bind(this, vnode),
|
||||||
|
}, [
|
||||||
|
m('label', 'Parent'),
|
||||||
|
m('select', {
|
||||||
|
onchange: this.updateParent.bind(this),
|
||||||
|
}, this.pages.map((item) => {
|
||||||
|
return m('option', {
|
||||||
|
value: item.id || 0,
|
||||||
|
selected: item.id === article.page_id
|
||||||
|
}, item.name)
|
||||||
|
})),
|
||||||
|
m('div.input-row', [
|
||||||
|
m('div.input-group', [
|
||||||
|
m('label', 'Name'),
|
||||||
|
m('input', {
|
||||||
|
type: 'text',
|
||||||
|
value: article.name,
|
||||||
|
oninput: this.updateValue.bind(this, 'name'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('div.input-group', [
|
||||||
|
m('label', 'Path'),
|
||||||
|
m('input', {
|
||||||
|
type: 'text',
|
||||||
|
value: article.path,
|
||||||
|
oninput: this.updateValue.bind(this, 'path'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('label', 'Description'),
|
||||||
|
m(Editor, {
|
||||||
|
oncreate: (subnode) => {
|
||||||
|
this.editor = subnode.state.editor
|
||||||
|
},
|
||||||
|
contentdata: article.content,
|
||||||
|
}),
|
||||||
|
m('div.input-row', [
|
||||||
|
m('div', [
|
||||||
|
m('label', 'Published at'),
|
||||||
|
m('input', {
|
||||||
|
type: 'text',
|
||||||
|
oncreate: (div) => {
|
||||||
|
if (!this.dateInstance) {
|
||||||
|
this.dateInstance = new dtsel.DTS(div.dom, {
|
||||||
|
dateFormat: 'yyyy-mm-dd',
|
||||||
|
timeFormat: 'HH:MM:SS',
|
||||||
|
showTime: true,
|
||||||
|
})
|
||||||
|
window.temp = this.dateInstance
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: article.publish_at.toISOString().replace('T', ', ').split('.')[0],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('div', [
|
||||||
|
m('label', 'Published by'),
|
||||||
|
m('select', {
|
||||||
|
onchange: this.updateStaffer.bind(this),
|
||||||
|
},
|
||||||
|
this.data.staff.map((item) => {
|
||||||
|
return m('option', {
|
||||||
|
value: item.id,
|
||||||
|
selected: item.id === article.admin_id
|
||||||
|
}, item.name)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
m('div.slim', [
|
||||||
|
m('label', 'Make featured'),
|
||||||
|
m('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: article.is_featured,
|
||||||
|
oninput: this.updateValue.bind(this, 'is_featured'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('div.actions', {
|
||||||
|
hidden: !article.name || !article.path
|
||||||
|
}, [
|
||||||
|
m('input', {
|
||||||
|
type: 'submit',
|
||||||
|
value: article.id ? 'Save' : 'Create',
|
||||||
|
}),
|
||||||
|
showPublish
|
||||||
|
? m('button', {
|
||||||
|
onclick: () => {
|
||||||
|
this.dateInstance.inputElem.value = (new Date().toISOString()).replace('T', ', ').split('.')[0]
|
||||||
|
}
|
||||||
|
}, 'Publish now')
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('files', [
|
||||||
|
m('h5', 'Files'),
|
||||||
|
this.data.files.map((file) => {
|
||||||
|
return m(
|
||||||
|
Fileinfo,
|
||||||
|
{ file: file },
|
||||||
|
m('div.remove',
|
||||||
|
m('button', { onclick: () => this.askConfirmRemoveFile(vnode, file) }, 'remove')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
article.id
|
||||||
|
? m('div.actions', [
|
||||||
|
m('button.fileupload', [
|
||||||
|
'Add file',
|
||||||
|
m('input', {
|
||||||
|
accept: '*',
|
||||||
|
type: 'file',
|
||||||
|
onchange: this.uploadFile.bind(this, vnode),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
/*
|
||||||
|
this.loading && !article
|
||||||
? m('div.admin-spinner.loading-spinner')
|
? m('div.admin-spinner.loading-spinner')
|
||||||
: null,
|
: null,
|
||||||
this.data.article
|
article
|
||||||
? m('div.admin-wrapper', [
|
? m('div.admin-wrapper', [
|
||||||
this.loading
|
this.loading
|
||||||
? m('div.loading-spinner')
|
? m('div.loading-spinner')
|
||||||
: null,
|
: null,
|
||||||
m('div.admin-actions', this.data.article.id
|
m('div.admin-actions', article.id
|
||||||
? [
|
? [
|
||||||
m('span', 'Actions:'),
|
m('span', 'Actions:'),
|
||||||
m(m.route.Link, { href: '/article/' + this.data.article.path }, 'View article'),
|
m(m.route.Link, { href: '/article/' + article.path }, 'View article'),
|
||||||
]
|
]
|
||||||
: null),
|
: null),
|
||||||
m('article.editarticle', [
|
m('article.editarticle', [
|
||||||
m('header', m('h1',
|
m('header', m('h1',
|
||||||
(this.data.article.id ? 'Edit ' : 'Create Article ') + (this.data.article.name || '(untitled)')
|
(article.id ? 'Edit ' : 'Create Article ') + (article.name || '(untitled)')
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
m('header', m('h1', this.creating ? 'Create Article' : 'Edit ' + (this.data.article.name || '(untitled)'))),
|
m('header', m('h1', this.creating ? 'Create Article' : 'Edit ' + (article.name || '(untitled)'))),
|
||||||
m('div.error', {
|
m('div.error', {
|
||||||
hidden: !this.error,
|
hidden: !this.error,
|
||||||
onclick: () => { vnode.state.error = '' },
|
onclick: () => { vnode.state.error = '' },
|
||||||
|
@ -233,7 +443,7 @@ const EditArticle = {
|
||||||
}, this.pages.map((item) => {
|
}, this.pages.map((item) => {
|
||||||
return m('option', {
|
return m('option', {
|
||||||
value: item.id || 0,
|
value: item.id || 0,
|
||||||
selected: item.id === this.data.article.page_id
|
selected: item.id === article.page_id
|
||||||
}, item.name)
|
}, item.name)
|
||||||
})),
|
})),
|
||||||
m('div.input-row', [
|
m('div.input-row', [
|
||||||
|
@ -241,7 +451,7 @@ const EditArticle = {
|
||||||
m('label', 'Name'),
|
m('label', 'Name'),
|
||||||
m('input', {
|
m('input', {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: this.data.article.name,
|
value: article.name,
|
||||||
oninput: this.updateValue.bind(this, 'name'),
|
oninput: this.updateValue.bind(this, 'name'),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
@ -249,7 +459,7 @@ const EditArticle = {
|
||||||
m('label', 'Path'),
|
m('label', 'Path'),
|
||||||
m('input', {
|
m('input', {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: this.data.article.path,
|
value: article.path,
|
||||||
oninput: this.updateValue.bind(this, 'path'),
|
oninput: this.updateValue.bind(this, 'path'),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
@ -259,7 +469,7 @@ const EditArticle = {
|
||||||
oncreate: (subnode) => {
|
oncreate: (subnode) => {
|
||||||
this.editor = subnode.state.editor
|
this.editor = subnode.state.editor
|
||||||
},
|
},
|
||||||
contentdata: this.data.article.content,
|
contentdata: article.content,
|
||||||
}),
|
}),
|
||||||
m('div.input-row', [
|
m('div.input-row', [
|
||||||
m('div.input-group', [
|
m('div.input-group', [
|
||||||
|
@ -276,7 +486,7 @@ const EditArticle = {
|
||||||
window.temp = this.dateInstance
|
window.temp = this.dateInstance
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
value: this.data.article.publish_at.toISOString().replace('T', ', ').split('.')[0],
|
value: article.publish_at.toISOString().replace('T', ', ').split('.')[0],
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
m('div.input-group', [
|
m('div.input-group', [
|
||||||
|
@ -287,7 +497,7 @@ const EditArticle = {
|
||||||
this.data.staff.map((item) => {
|
this.data.staff.map((item) => {
|
||||||
return m('option', {
|
return m('option', {
|
||||||
value: item.id,
|
value: item.id,
|
||||||
selected: item.id === this.data.article.admin_id
|
selected: item.id === article.admin_id
|
||||||
}, item.name)
|
}, item.name)
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
@ -296,13 +506,13 @@ const EditArticle = {
|
||||||
m('label', 'Make featured'),
|
m('label', 'Make featured'),
|
||||||
m('input', {
|
m('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
checked: this.data.article.is_featured,
|
checked: article.is_featured,
|
||||||
oninput: this.updateValue.bind(this, 'is_featured'),
|
oninput: this.updateValue.bind(this, 'is_featured'),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
m('div', {
|
m('div', {
|
||||||
hidden: !this.data.article.name || !this.data.article.path
|
hidden: !article.name || !article.path
|
||||||
}, [
|
}, [
|
||||||
m('input', {
|
m('input', {
|
||||||
type: 'submit',
|
type: 'submit',
|
||||||
|
@ -311,7 +521,7 @@ const EditArticle = {
|
||||||
showPublish
|
showPublish
|
||||||
? m('button.submit', {
|
? m('button.submit', {
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
this.data.article.publish_at = new Date().toISOString()
|
article.publish_at = new Date().toISOString()
|
||||||
}
|
}
|
||||||
}, 'Publish')
|
}, 'Publish')
|
||||||
: null,
|
: null,
|
||||||
|
@ -325,7 +535,7 @@ const EditArticle = {
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
: null,
|
: null,
|
||||||
this.data.article.id
|
article.id
|
||||||
? m('div.fileupload', [
|
? m('div.fileupload', [
|
||||||
'Add file',
|
'Add file',
|
||||||
m('input', {
|
m('input', {
|
||||||
|
@ -341,7 +551,7 @@ const EditArticle = {
|
||||||
: m('div.error', {
|
: m('div.error', {
|
||||||
hidden: !this.error,
|
hidden: !this.error,
|
||||||
onclick: () => { this.fetchArticle(vnode) },
|
onclick: () => { this.fetchArticle(vnode) },
|
||||||
}, this.error),,
|
}, this.error),,*/
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -182,27 +182,29 @@ const AdminEditPage = {
|
||||||
media: bannerImage,
|
media: bannerImage,
|
||||||
}, 'Click to upload banner image')
|
}, 'Click to upload banner image')
|
||||||
: null,
|
: null,
|
||||||
m('div.inside.vertical', {
|
m('div.inside.vertical', [
|
||||||
onsubmit: this.save.bind(this, vnode),
|
m('div.actions', [
|
||||||
}, [
|
|
||||||
m('div.page-goback', [
|
|
||||||
'« ',
|
'« ',
|
||||||
m(m.route.Link, { href: '/admin/pages' }, 'Pages')
|
m(m.route.Link, { href: '/admin/pages' }, 'Pages'),
|
||||||
|
page
|
||||||
|
? [
|
||||||
|
m('div.filler'),
|
||||||
|
'Actions:',
|
||||||
|
m(m.route.Link, { href: '/page/' + page.path }, 'View page'),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
]),
|
]),
|
||||||
m('h2.title', this.lastid === 'add' ? 'Create page' : 'Edit ' + (page && page.name || '(untitled)')),
|
m('h2.title', this.lastid === 'add' ? 'Create page' : 'Edit ' + (page && page.name || '(untitled)')),
|
||||||
page
|
m('div.container', [
|
||||||
? m('div.actions', [
|
|
||||||
m('span', 'Actions:'),
|
|
||||||
m(m.route.Link, { href: '/page/' + page.path }, 'View page'),
|
|
||||||
])
|
|
||||||
: null,
|
|
||||||
m('div.error', {
|
m('div.error', {
|
||||||
hidden: !this.error,
|
hidden: !this.error,
|
||||||
onclick: function() { vnode.state.error = '' },
|
onclick: function() { vnode.state.error = '' },
|
||||||
}, this.error),
|
}, this.error),
|
||||||
this.loading
|
this.loading
|
||||||
? m('div.loading-spinner')
|
? m('div.loading-spinner')
|
||||||
: [
|
: null,
|
||||||
|
page
|
||||||
|
? [
|
||||||
m(FileUpload, {
|
m(FileUpload, {
|
||||||
class: 'cover',
|
class: 'cover',
|
||||||
useimg: true,
|
useimg: true,
|
||||||
|
@ -210,7 +212,9 @@ const AdminEditPage = {
|
||||||
ondelete: this.mediaRemoved.bind(this, 'media'),
|
ondelete: this.mediaRemoved.bind(this, 'media'),
|
||||||
media: mediaImage,
|
media: mediaImage,
|
||||||
}, 'Click to upload page image'),
|
}, 'Click to upload page image'),
|
||||||
m('form', [
|
m('form', {
|
||||||
|
onsubmit: this.save.bind(this, vnode),
|
||||||
|
}, [
|
||||||
m('label', 'Parent'),
|
m('label', 'Parent'),
|
||||||
m('select', {
|
m('select', {
|
||||||
onchange: this.updateParent.bind(this),
|
onchange: this.updateParent.bind(this),
|
||||||
|
@ -253,7 +257,9 @@ const AdminEditPage = {
|
||||||
value: 'Save',
|
value: 'Save',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
],
|
]
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ const AdminPages = {
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
this.error = ''
|
this.error = ''
|
||||||
this.pages = []
|
this.pages = []
|
||||||
this.removePage = null
|
|
||||||
|
|
||||||
document.title = 'Pages - Admin NFP Moe'
|
document.title = 'Pages - Admin NFP Moe'
|
||||||
this.fetchPages(vnode)
|
this.fetchPages(vnode)
|
||||||
|
@ -32,24 +31,24 @@ const AdminPages = {
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmRemovePage: function(vnode, page) {
|
confirmRemovePage: function(vnode, page) {
|
||||||
let removingPage = this.removePage
|
|
||||||
this.removePage = null
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
m.redraw()
|
m.redraw()
|
||||||
|
|
||||||
return api.sendRequest({
|
return api.sendRequest({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: '/api/auth/pages/' + removingPage.id,
|
url: '/api/auth/pages/' + page.id,
|
||||||
})
|
})
|
||||||
.then(() => PageTree.refreshTree())
|
|
||||||
.then(
|
.then(
|
||||||
() => this.fetchPages(vnode),
|
() => Promise.all([
|
||||||
(err) => {
|
PageTree.refreshTree(),
|
||||||
this.error = err.message
|
this.fetchPages(),
|
||||||
|
]),
|
||||||
|
(err) => { this.error = err.message }
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
m.redraw()
|
m.redraw()
|
||||||
}
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
askConfirmRemovePage: function(vnode, page) {
|
askConfirmRemovePage: function(vnode, page) {
|
||||||
|
@ -61,7 +60,7 @@ const AdminPages = {
|
||||||
'Don\'t remove',
|
'Don\'t remove',
|
||||||
'',
|
'',
|
||||||
page,
|
page,
|
||||||
this.confirmRemovePage.bind(this))
|
this.confirmRemovePage.bind(this, vnode))
|
||||||
},
|
},
|
||||||
|
|
||||||
drawPage: function(vnode, page) {
|
drawPage: function(vnode, page) {
|
||||||
|
@ -82,12 +81,13 @@ const AdminPages = {
|
||||||
return [
|
return [
|
||||||
m('div.admin', [
|
m('div.admin', [
|
||||||
m('div.inside.vertical', [
|
m('div.inside.vertical', [
|
||||||
m('div.spacer'),
|
|
||||||
m('h2.title', 'All pages'),
|
|
||||||
m('div.actions', [
|
m('div.actions', [
|
||||||
|
m('div.filler'),
|
||||||
m('span', 'Actions:'),
|
m('span', 'Actions:'),
|
||||||
m(m.route.Link, { href: '/admin/pages/add' }, 'Create new page'),
|
m(m.route.Link, { href: '/admin/pages/add' }, 'Create new page'),
|
||||||
]),
|
]),
|
||||||
|
m('h2.title', 'All pages'),
|
||||||
|
m('div.container', [
|
||||||
m('div.error', {
|
m('div.error', {
|
||||||
hidden: !this.error,
|
hidden: !this.error,
|
||||||
onclick: function() { vnode.state.error = '' },
|
onclick: function() { vnode.state.error = '' },
|
||||||
|
@ -106,10 +106,7 @@ const AdminPages = {
|
||||||
m('tbody', this.pages.map(AdminPages.drawPage.bind(this, vnode))),
|
m('tbody', this.pages.map(AdminPages.drawPage.bind(this, vnode))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
/*m(Pages, {
|
]),
|
||||||
base: '/admin/articles',
|
|
||||||
links: this.links,
|
|
||||||
}),*/
|
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
|
|
|
@ -85,6 +85,7 @@ const Fileinfo = {
|
||||||
&& vnode.attrs.file.meta.torrent.files.length > 4
|
&& vnode.attrs.file.meta.torrent.files.length > 4
|
||||||
? m('div.trimmed', '...' + vnode.attrs.file.meta.torrent.files.length + ' files...')
|
? m('div.trimmed', '...' + vnode.attrs.file.meta.torrent.files.length + ' files...')
|
||||||
: null,
|
: null,
|
||||||
|
vnode.children,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ const Menu = {
|
||||||
'Welcome ' + Authentication.currentUser.name + '. ',
|
'Welcome ' + Authentication.currentUser.name + '. ',
|
||||||
m('button', { onclick: this.logOut }, '(Log out)'),
|
m('button', { onclick: this.logOut }, '(Log out)'),
|
||||||
]),
|
]),
|
||||||
m('div.actions', [
|
m('div', [
|
||||||
m(m.route.Link, { href: '/admin/articles/add' }, 'Create article'),
|
m(m.route.Link, { href: '/admin/articles/add' }, 'Create article'),
|
||||||
m(m.route.Link, { href: '/admin/articles' }, 'Articles'),
|
m(m.route.Link, { href: '/admin/articles' }, 'Articles'),
|
||||||
m(m.route.Link, { href: '/admin/pages' }, 'Pages'),
|
m(m.route.Link, { href: '/admin/pages' }, 'Pages'),
|
||||||
|
|
|
@ -90,12 +90,21 @@ const SiteArticle = {
|
||||||
: null,
|
: null,
|
||||||
(article
|
(article
|
||||||
? m('.inside.vertical', [
|
? m('.inside.vertical', [
|
||||||
m('div.page-goback', ['« ', m(m.route.Link, {
|
m('div.actions', [
|
||||||
|
'« ',
|
||||||
|
m(m.route.Link, {
|
||||||
href: article.page_path
|
href: article.page_path
|
||||||
? '/page/' + article.page_path
|
? '/page/' + article.page_path
|
||||||
: '/'
|
: '/'
|
||||||
}, article.page_name || 'Home')]
|
}, article.page_name || 'Home'),
|
||||||
),
|
Authentication.currentUser
|
||||||
|
? [
|
||||||
|
m('div.filler'),
|
||||||
|
'Actions:',
|
||||||
|
m(m.route.Link, { href: '/admin/articles/' + article.id }, 'Edit article'),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
article ? m(Article, { full: true, files: this.data.files, article: article }) : null,
|
article ? m(Article, { full: true, files: this.data.files, article: article }) : null,
|
||||||
window.LoadComments
|
window.LoadComments
|
||||||
? m('div#hyvor-talk-view', { oncreate: function() {
|
? m('div#hyvor-talk-view', { oncreate: function() {
|
||||||
|
|
|
@ -145,7 +145,7 @@ const SitePage = {
|
||||||
: null),
|
: null),
|
||||||
(page
|
(page
|
||||||
? m('.inside.vertical', [
|
? m('.inside.vertical', [
|
||||||
m('div.page-goback', [
|
m('div.actions', [
|
||||||
'« ',
|
'« ',
|
||||||
m(m.route.Link, {
|
m(m.route.Link, {
|
||||||
href: page.parent_path
|
href: page.parent_path
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
--admin-bg: hsl(213.9, 100%, 95%);
|
--admin-bg: hsl(213.9, 100%, 95%);
|
||||||
|
--admin-bg-highlight: hsl(213.9, 100%, 85%);
|
||||||
--admin-color: #000;
|
--admin-color: #000;
|
||||||
--admin-table-border: #01579b;
|
--admin-table-border: #01579b;
|
||||||
--admin-table-header-bg: #3D77C7;
|
--admin-table-header-bg: #3D77C7;
|
||||||
|
@ -18,27 +19,27 @@
|
||||||
.admin {
|
.admin {
|
||||||
background: var(--admin-bg);
|
background: var(--admin-bg);
|
||||||
color: var(--admin-color);
|
color: var(--admin-color);
|
||||||
display: flex;
|
min-height: calc(100vh - 390px);
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: calc(100vh - 200px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin .inside {
|
|
||||||
padding: 0 1rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin .spacer {
|
|
||||||
height: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin .loading-spinner {
|
.admin .loading-spinner {
|
||||||
position: relative;
|
position: absolute;
|
||||||
left: unset;
|
left: 0;
|
||||||
top: unset;
|
top: 0;
|
||||||
min-height: 300px;
|
width: 100%;
|
||||||
height: calc(100vh - 300px);
|
height: 100%;
|
||||||
|
background: #0002
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin .container .actions {
|
||||||
|
margin-left: -2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin .container .actions button,
|
||||||
|
.admin .container .actions input {
|
||||||
|
margin-left: 1rem;
|
||||||
|
font-weight: normal;
|
||||||
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin table {
|
.admin table {
|
||||||
|
@ -47,6 +48,14 @@
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin table thead th,
|
.admin table thead th,
|
||||||
|
@ -65,6 +74,10 @@
|
||||||
color: var(--alt-color);
|
color: var(--alt-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin table tr:hover td {
|
||||||
|
background: var(--admin-bg-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
.admin table button {
|
.admin table button {
|
||||||
color: var(--link);
|
color: var(--link);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
@ -78,17 +91,6 @@
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin .actions {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin .actions a {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin form {
|
.admin form {
|
||||||
margin: 1rem 0 0;
|
margin: 1rem 0 0;
|
||||||
}
|
}
|
||||||
|
@ -108,12 +110,55 @@
|
||||||
flex: 2 1 50px;
|
flex: 2 1 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin .input-row > .slim {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin .error {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -30%;
|
||||||
|
width: 60%;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ************** fileinfo ************** */
|
||||||
|
|
||||||
|
.admin fileinfo:hover {
|
||||||
|
background: var(--admin-bg-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin fileinfo .remove {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to right, transparent, var(--admin-bg) 2rem);
|
||||||
|
padding-left: 3rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin fileinfo:hover .remove {
|
||||||
|
background: linear-gradient(to right, transparent, var(--admin-bg-highlight) 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin fileinfo .remove button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ************** fileupload ************** */
|
/* ************** fileupload ************** */
|
||||||
|
|
||||||
fileupload {
|
fileupload {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileupload,
|
||||||
|
.fileupload {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileupload.banner {
|
fileupload.banner {
|
||||||
|
@ -151,7 +196,8 @@ fileupload .text {
|
||||||
color: var(--seperator);
|
color: var(--seperator);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileupload input {
|
fileupload input,
|
||||||
|
.fileupload input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -189,9 +235,6 @@ dialogue {
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogue h2 {
|
|
||||||
}
|
|
||||||
|
|
||||||
dialogue p {
|
dialogue p {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -233,6 +276,8 @@ dialogue button.cancel {
|
||||||
===================== 3rd party =====================
|
===================== 3rd party =====================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* ************** Editor ************** */
|
||||||
|
|
||||||
.ce-block__content,
|
.ce-block__content,
|
||||||
.ce-toolbar__content { max-width:calc(100% - 120px) !important; }
|
.ce-toolbar__content { max-width:calc(100% - 120px) !important; }
|
||||||
.cdx-block { max-width: 100% !important; }
|
.cdx-block { max-width: 100% !important; }
|
||||||
|
@ -241,6 +286,7 @@ dialogue button.cancel {
|
||||||
border: 1px solid var(--color);
|
border: 1px solid var(--color);
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
|
padding-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.codex-editor:hover,
|
.codex-editor:hover,
|
||||||
|
@ -248,3 +294,102 @@ dialogue button.cancel {
|
||||||
border-color: var(--link);
|
border-color: var(--link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ************** dte ************** */
|
||||||
|
|
||||||
|
.date-selector-wrapper {
|
||||||
|
width: 200px;
|
||||||
|
padding: 3px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 1px 1px 10px 1px #5c5c5c;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 12px;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
z-index: 10;
|
||||||
|
/* user-select: none; */
|
||||||
|
}
|
||||||
|
.cal-header, .cal-row {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.cal-cell, .cal-nav {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.cal-day-names {
|
||||||
|
height: 25px;
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
.cal-day-names .cal-cell {
|
||||||
|
cursor: default;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.cal-cell-prev, .cal-cell-next {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
.cal-months .cal-row, .cal-years .cal-row {
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
.cal-nav-prev, .cal-nav-next {
|
||||||
|
flex: 0.15;
|
||||||
|
}
|
||||||
|
.cal-nav-current {
|
||||||
|
flex: 0.75;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.cal-months .cal-cell, .cal-years .cal-cell {
|
||||||
|
flex: 0.25;
|
||||||
|
}
|
||||||
|
.cal-days .cal-cell {
|
||||||
|
flex: 0.143;
|
||||||
|
}
|
||||||
|
.cal-value {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #286090;
|
||||||
|
}
|
||||||
|
.cal-cell:hover, .cal-nav:hover {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
.cal-value:hover {
|
||||||
|
background-color: #204d74;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* time footer */
|
||||||
|
.cal-time {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
height: 27px;
|
||||||
|
line-height: 27px;
|
||||||
|
}
|
||||||
|
.cal-time-label, .cal-time-value {
|
||||||
|
flex: 0.12;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.cal-time-slider {
|
||||||
|
flex: 0.77;
|
||||||
|
background-image: linear-gradient(to right, #d1d8dd, #d1d8dd);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 1px;
|
||||||
|
background-position: left 50%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.cal-time-slider input {
|
||||||
|
width: 100%;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 100%;
|
||||||
|
outline: 0;
|
||||||
|
user-select: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ce-block__content,
|
||||||
|
.ce-toolbar__content { max-width:calc(100% - 120px) !important; }
|
||||||
|
.cdx-block { max-width: 100% !important; }
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ a {
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +131,7 @@ select {
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
|
line-height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
@ -272,9 +274,13 @@ header aside {
|
||||||
padding: 0.5rem 0.5rem;
|
padding: 0.5rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
header aside .actions a {
|
header aside a,
|
||||||
margin-left: 1rem;
|
header aside button {
|
||||||
display: inline-block;
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header aside p button {
|
||||||
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avifsupport header .logo {
|
.avifsupport header .logo {
|
||||||
|
@ -335,12 +341,12 @@ main {
|
||||||
text-shadow: 0 0 .3em #000;
|
text-shadow: 0 0 .3em #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-goback {
|
.actions {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-goback a {
|
.actions a {
|
||||||
margin-left: 0.375rem;
|
margin-left: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,6 +526,7 @@ fileinfo {
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
display: block;
|
display: block;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
fileinfo.slim {
|
fileinfo.slim {
|
||||||
|
|
BIN
nfp_moe/public/assets/img/correct_asuna_frontpage.xcf
Normal file
BIN
nfp_moe/public/assets/img/correct_asuna_frontpage.xcf
Normal file
Binary file not shown.
BIN
nfp_moe/public/assets/img/correct_asuna_frontpage_night.xcf
Normal file
BIN
nfp_moe/public/assets/img/correct_asuna_frontpage_night.xcf
Normal file
Binary file not shown.
BIN
nfp_moe/public/assets/img/correct_footer.xcf
Normal file
BIN
nfp_moe/public/assets/img/correct_footer.xcf
Normal file
Binary file not shown.
BIN
nfp_moe/public/assets/img/correct_logo.xcf
Normal file
BIN
nfp_moe/public/assets/img/correct_logo.xcf
Normal file
Binary file not shown.
Loading…
Reference in a new issue