Remove google, add avif support
This commit is contained in:
parent
85d31ded75
commit
44bcbe2647
21 changed files with 178 additions and 194 deletions
|
@ -27,7 +27,7 @@ jobs:
|
||||||
name: Push to docker
|
name: Push to docker
|
||||||
command: |
|
command: |
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||||
docker push ${di}
|
docker push ${di} --all-tags
|
||||||
- deploy:
|
- deploy:
|
||||||
name: Deploy to production
|
name: Deploy to production
|
||||||
command: |
|
command: |
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import googleauth from 'google-auth-library'
|
|
||||||
import config from '../config.mjs'
|
|
||||||
|
|
||||||
const oauth2Client = new googleauth.OAuth2Client(config.get('googleid'))
|
|
||||||
|
|
||||||
// This is hard to have always running as it requires a
|
|
||||||
// test access token which always expire.
|
|
||||||
|
|
||||||
/* istanbul ignore next */
|
|
||||||
export function getProfile(token) {
|
|
||||||
return oauth2Client.getTokenInfo(token)
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Staff from '../staff/model.mjs'
|
import Staff from '../staff/model.mjs'
|
||||||
import Jwt from '../jwt.mjs'
|
import Jwt from '../jwt.mjs'
|
||||||
import * as google from './google.mjs'
|
|
||||||
import * as security from './security.mjs'
|
import * as security from './security.mjs'
|
||||||
import AuthHelper from './helper.mjs'
|
import AuthHelper from './helper.mjs'
|
||||||
|
|
||||||
|
@ -10,37 +9,10 @@ export default class AuthRoutes {
|
||||||
helper: opts.helper || new AuthHelper(),
|
helper: opts.helper || new AuthHelper(),
|
||||||
Staff: opts.Staff || Staff,
|
Staff: opts.Staff || Staff,
|
||||||
jwt: opts.jwt || new Jwt(),
|
jwt: opts.jwt || new Jwt(),
|
||||||
google: opts.google || google,
|
|
||||||
security: opts.security || security,
|
security: opts.security || security,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* POST /api/login - Authenticate a user using social login
|
|
||||||
*
|
|
||||||
* @body {string} token - The google token to authenticate
|
|
||||||
* @returns
|
|
||||||
*
|
|
||||||
* { token: 'Authentication token' }
|
|
||||||
*/
|
|
||||||
async login(ctx) {
|
|
||||||
let output = await google.getProfile(ctx.request.body.token)
|
|
||||||
|
|
||||||
if (output.email_verified !== 'true') ctx.throw(422, 'Email was not verified with google')
|
|
||||||
if (!output.email) ctx.throw(422, 'Email was missing from google response')
|
|
||||||
|
|
||||||
let level = 1
|
|
||||||
let staff = await this.Staff
|
|
||||||
.query({ where: { email: output.email }})
|
|
||||||
.fetch({ require: false })
|
|
||||||
|
|
||||||
if (staff && staff.id && staff.get('level')) {
|
|
||||||
level = staff.get('level')
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.body = { token: this.jwt.createToken(staff.id, output.email, level) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* POST /api/login/user - Authenticate a user using password login
|
* POST /api/login/user - Authenticate a user using password login
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import knex from 'knex'
|
import knex from 'knex-core'
|
||||||
import bookshelf from 'bookshelf'
|
import bookshelf from 'bookshelf'
|
||||||
|
|
||||||
import config from './config.mjs'
|
import config from './config.mjs'
|
||||||
|
@ -178,7 +178,7 @@ function getConfig(index = 0, addEvents = true) {
|
||||||
afterCreate: addEvents && afterCreate || null,
|
afterCreate: addEvents && afterCreate || null,
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 10,
|
max: 10,
|
||||||
beforeDestroy: addEvents && beforeDestroy || null,
|
// beforeDestroy: addEvents && beforeDestroy || null,
|
||||||
},
|
},
|
||||||
acquireConnectionTimeout: 10000,
|
acquireConnectionTimeout: 10000,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import nconf from 'nconf'
|
import nconf from 'nconf-lite'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
|
|
||||||
// Helper method for global usage.
|
// Helper method for global usage.
|
||||||
|
|
|
@ -39,6 +39,21 @@ const Media = bookshelf.createModel({
|
||||||
return `${Media.baseUrl}${this.get('large_image')}`
|
return `${Media.baseUrl}${this.get('large_image')}`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
small_url_avif() {
|
||||||
|
if (!this.get('small_image_avif')) return null
|
||||||
|
return `${Media.baseUrl}${this.get('small_image_avif')}`
|
||||||
|
},
|
||||||
|
|
||||||
|
medium_url_avif() {
|
||||||
|
if (!this.get('small_image_avif')) return null
|
||||||
|
return `${Media.baseUrl}${this.get('medium_image_avif')}`
|
||||||
|
},
|
||||||
|
|
||||||
|
large_url_avif() {
|
||||||
|
if (!this.get('small_image_avif')) return null
|
||||||
|
return `${Media.baseUrl}${this.get('large_image_avif')}`
|
||||||
|
},
|
||||||
|
|
||||||
link() {
|
link() {
|
||||||
return `${Media.baseUrl}${this.get('org_image')}`
|
return `${Media.baseUrl}${this.get('org_image')}`
|
||||||
},
|
},
|
||||||
|
@ -54,7 +69,7 @@ const Media = bookshelf.createModel({
|
||||||
}, {
|
}, {
|
||||||
baseUrl: config.get('upload:baseurl'),
|
baseUrl: config.get('upload:baseurl'),
|
||||||
|
|
||||||
getSubUrl(input, size) {
|
getSubUrl(input, size, type = 'jpg') {
|
||||||
if (!input) return input
|
if (!input) return input
|
||||||
|
|
||||||
let output = input
|
let output = input
|
||||||
|
@ -62,7 +77,7 @@ const Media = bookshelf.createModel({
|
||||||
let ext = path.extname(input).toLowerCase()
|
let ext = path.extname(input).toLowerCase()
|
||||||
output = input.slice(0, -ext.length)
|
output = input.slice(0, -ext.length)
|
||||||
}
|
}
|
||||||
return `${output}.${size}.jpg`
|
return `${output}.${size}.${type}`
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,49 +9,85 @@ export default class Resizer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
createSmall(input) {
|
createSmall(input, format = 'jpg') {
|
||||||
let output = this.Media.getSubUrl(input, 'small')
|
let output = this.Media.getSubUrl(input, 'small', format === 'avif' ? 'avif' : 'jpg')
|
||||||
|
|
||||||
return this.sharp(input)
|
let sized = this.sharp(input)
|
||||||
.resize(360, 360, {
|
.resize(500, 500, {
|
||||||
fit: sharp.fit.inside,
|
fit: sharp.fit.inside,
|
||||||
withoutEnlargement: true,
|
withoutEnlargement: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (format === 'avif') {
|
||||||
|
return sized
|
||||||
|
.avif({
|
||||||
|
quality: 80,
|
||||||
|
speed: 5,
|
||||||
|
})
|
||||||
|
.toFile(output)
|
||||||
|
.then(() => output)
|
||||||
|
} else {
|
||||||
|
return sized
|
||||||
.jpeg({
|
.jpeg({
|
||||||
quality: 90,
|
quality: 93,
|
||||||
})
|
})
|
||||||
.toFile(output)
|
.toFile(output)
|
||||||
.then(() => output)
|
.then(() => output)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createMedium(input, height) {
|
createMedium(input, height, format = 'jpg') {
|
||||||
let output = this.Media.getSubUrl(input, 'medium')
|
let output = this.Media.getSubUrl(input, 'medium', format === 'avif' ? 'avif' : 'jpg')
|
||||||
|
|
||||||
return this.sharp(input)
|
let sized = this.sharp(input)
|
||||||
.resize(700, height || 700, {
|
.resize(800, height || 800, {
|
||||||
fit: height && sharp.fit.cover || sharp.fit.inside,
|
fit: height && sharp.fit.cover || sharp.fit.inside,
|
||||||
withoutEnlargement: true,
|
withoutEnlargement: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (format === 'avif') {
|
||||||
|
return sized
|
||||||
|
.avif({
|
||||||
|
quality: 80,
|
||||||
|
speed: 5,
|
||||||
|
})
|
||||||
|
.toFile(output)
|
||||||
|
.then(() => output)
|
||||||
|
} else {
|
||||||
|
return sized
|
||||||
.jpeg({
|
.jpeg({
|
||||||
quality: 90,
|
quality: 93,
|
||||||
})
|
})
|
||||||
.toFile(output)
|
.toFile(output)
|
||||||
.then(() => output)
|
.then(() => output)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createLarge(input) {
|
createLarge(input, format = 'jpg') {
|
||||||
let output = this.Media.getSubUrl(input, 'large')
|
let output = this.Media.getSubUrl(input, 'large', format === 'avif' ? 'avif' : 'jpg')
|
||||||
|
|
||||||
return this.sharp(input)
|
let sized = this.sharp(input)
|
||||||
.resize(1280, 1280, {
|
.resize(1280, 1280, {
|
||||||
fit: sharp.fit.inside,
|
fit: sharp.fit.inside,
|
||||||
withoutEnlargement: true,
|
withoutEnlargement: true,
|
||||||
})
|
})
|
||||||
.jpeg({
|
|
||||||
quality: 90,
|
if (format === 'avif') {
|
||||||
|
return sized
|
||||||
|
.avif({
|
||||||
|
quality: 85,
|
||||||
|
speed: 5,
|
||||||
})
|
})
|
||||||
.toFile(output)
|
.toFile(output)
|
||||||
.then(() => output)
|
.then(() => output)
|
||||||
|
} else {
|
||||||
|
return sized
|
||||||
|
.jpeg({
|
||||||
|
quality: 93,
|
||||||
|
})
|
||||||
|
.toFile(output)
|
||||||
|
.then(() => output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
autoRotate(input) {
|
autoRotate(input) {
|
||||||
|
|
|
@ -27,14 +27,20 @@ export default class MediaRoutes {
|
||||||
let smallPath = await this.resize.createSmall(result.path)
|
let smallPath = await this.resize.createSmall(result.path)
|
||||||
let mediumPath = await this.resize.createMedium(result.path, height)
|
let mediumPath = await this.resize.createMedium(result.path, height)
|
||||||
let largePath = await this.resize.createLarge(result.path)
|
let largePath = await this.resize.createLarge(result.path)
|
||||||
|
let smallPathAvif = await this.resize.createSmall(result.path, 'avif')
|
||||||
|
let mediumPathAvif = await this.resize.createMedium(result.path, height, 'avif')
|
||||||
|
let largePathAvif = await this.resize.createLarge(result.path, 'avif')
|
||||||
|
|
||||||
let token = this.jwt.signDirect({ site: config.get('upload:name') }, config.get('upload:secret'))
|
let token = this.jwt.signDirect({ site: config.get('upload:name') }, config.get('upload:secret'))
|
||||||
|
|
||||||
let [org, small, medium, large] = await Promise.all([
|
let [org, small, medium, large, smallAvif, mediumAvif, largeAvif] = await Promise.all([
|
||||||
this.uploadFile(token, result.path),
|
this.uploadFile(token, result.path),
|
||||||
this.uploadFile(token, smallPath),
|
this.uploadFile(token, smallPath),
|
||||||
this.uploadFile(token, mediumPath),
|
this.uploadFile(token, mediumPath),
|
||||||
this.uploadFile(token, largePath),
|
this.uploadFile(token, largePath),
|
||||||
|
this.uploadFile(token, smallPathAvif),
|
||||||
|
this.uploadFile(token, mediumPathAvif),
|
||||||
|
this.uploadFile(token, largePathAvif),
|
||||||
])
|
])
|
||||||
|
|
||||||
ctx.body = await this.Media.create({
|
ctx.body = await this.Media.create({
|
||||||
|
@ -43,6 +49,9 @@ export default class MediaRoutes {
|
||||||
small_image: small.path,
|
small_image: small.path,
|
||||||
medium_image: medium.path,
|
medium_image: medium.path,
|
||||||
large_image: large.path,
|
large_image: large.path,
|
||||||
|
small_image_avif: smallAvif.path,
|
||||||
|
medium_image_avif: mediumAvif.path,
|
||||||
|
large_image_avif: largeAvif.path,
|
||||||
org_image: org.path,
|
org_image: org.path,
|
||||||
size: result.size,
|
size: result.size,
|
||||||
staff_id: ctx.state.user.id,
|
staff_id: ctx.state.user.id,
|
||||||
|
|
|
@ -14,7 +14,6 @@ const router = new Router()
|
||||||
|
|
||||||
// API Authentication
|
// API Authentication
|
||||||
const authentication = new AuthRoutes()
|
const authentication = new AuthRoutes()
|
||||||
router.post('/api/login', authentication.login.bind(authentication))
|
|
||||||
router.post('/api/login/user', authentication.loginUser.bind(authentication))
|
router.post('/api/login/user', authentication.loginUser.bind(authentication))
|
||||||
|
|
||||||
// API Media
|
// API Media
|
||||||
|
|
|
@ -24,13 +24,19 @@ function mapArticle(trim = false, x, includeBanner = false, includeFiles = true)
|
||||||
media: x.media && ({
|
media: x.media && ({
|
||||||
link: !trim && x.media.link || null,
|
link: !trim && x.media.link || null,
|
||||||
large_url: x.media.large_url,
|
large_url: x.media.large_url,
|
||||||
|
large_url_avif: x.media.large_url_avif,
|
||||||
medium_url: x.media.medium_url,
|
medium_url: x.media.medium_url,
|
||||||
|
medium_url_avif: x.media.medium_url_avif,
|
||||||
small_url: x.media.small_url,
|
small_url: x.media.small_url,
|
||||||
|
small_url_avif: x.media.small_url_avif,
|
||||||
}) || null,
|
}) || null,
|
||||||
banner: x.banner && includeBanner && ({
|
banner: x.banner && includeBanner && ({
|
||||||
large_url: x.banner.large_url,
|
large_url: x.banner.large_url,
|
||||||
|
large_url_avif: x.banner.large_url_avif,
|
||||||
medium_url: x.banner.medium_url,
|
medium_url: x.banner.medium_url,
|
||||||
|
medium_url_avif: x.banner.medium_url_avif,
|
||||||
small_url: x.banner.small_url,
|
small_url: x.banner.small_url,
|
||||||
|
small_url_avif: x.banner.small_url_avif,
|
||||||
}) || null,
|
}) || null,
|
||||||
parent: x.parent && ({
|
parent: x.parent && ({
|
||||||
id: x.parent.id,
|
id: x.parent.id,
|
||||||
|
@ -67,8 +73,11 @@ function mapPage(x) {
|
||||||
media: x.media && ({
|
media: x.media && ({
|
||||||
link: x.media.link,
|
link: x.media.link,
|
||||||
large_url: x.media.large_url,
|
large_url: x.media.large_url,
|
||||||
|
large_url_avif: x.media.large_url_avif,
|
||||||
medium_url: x.media.medium_url,
|
medium_url: x.media.medium_url,
|
||||||
|
medium_url_avif: x.media.medium_url_avif,
|
||||||
small_url: x.media.small_url,
|
small_url: x.media.small_url,
|
||||||
|
small_url_avif: x.media.small_url_avif,
|
||||||
}) || null,
|
}) || null,
|
||||||
parent: x.parent && ({
|
parent: x.parent && ({
|
||||||
id: x.parent.id,
|
id: x.parent.id,
|
||||||
|
@ -77,8 +86,11 @@ function mapPage(x) {
|
||||||
}),
|
}),
|
||||||
banner: x.banner && ({
|
banner: x.banner && ({
|
||||||
large_url: x.banner.large_url,
|
large_url: x.banner.large_url,
|
||||||
|
large_url_avif: x.banner.large_url_avif,
|
||||||
medium_url: x.banner.medium_url,
|
medium_url: x.banner.medium_url,
|
||||||
|
medium_url_avif: x.banner.medium_url_avif,
|
||||||
small_url: x.banner.small_url,
|
small_url: x.banner.small_url,
|
||||||
|
small_url_avif: x.banner.small_url_avif,
|
||||||
}) || null,
|
}) || null,
|
||||||
children: x.children && x.children.map(f => ({
|
children: x.children && x.children.map(f => ({
|
||||||
id: f.id,
|
id: f.id,
|
||||||
|
@ -94,7 +106,8 @@ export async function serveIndex(ctx, path) {
|
||||||
let links = null
|
let links = null
|
||||||
let featured = null
|
let featured = null
|
||||||
let url = frontend + ctx.request.url
|
let url = frontend + ctx.request.url
|
||||||
let image = frontend + '/assets/img/heart.jpg'
|
let image = frontend + '/assets/img/heart.png'
|
||||||
|
let image_avif = frontend + '/assets/img/heart.png'
|
||||||
let title = 'NFP Moe - Anime/Manga translation group'
|
let title = 'NFP Moe - Anime/Manga translation group'
|
||||||
let description = 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.'
|
let description = 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.'
|
||||||
try {
|
try {
|
||||||
|
@ -147,8 +160,10 @@ export async function serveIndex(ctx, path) {
|
||||||
if (found) {
|
if (found) {
|
||||||
if (found.media) {
|
if (found.media) {
|
||||||
image = found.media.large_url
|
image = found.media.large_url
|
||||||
|
image_avif = found.media.large_url_avifl
|
||||||
} else if (found.banner) {
|
} else if (found.banner) {
|
||||||
image = found.banner.large_url
|
image = found.banner.large_url
|
||||||
|
image_avif = found.banner.large_url_avifl
|
||||||
}
|
}
|
||||||
if (found.description) {
|
if (found.description) {
|
||||||
description = striptags(found.description)
|
description = striptags(found.description)
|
||||||
|
@ -174,6 +189,7 @@ export async function serveIndex(ctx, path) {
|
||||||
featured: JSON.stringify(featured),
|
featured: JSON.stringify(featured),
|
||||||
url: url,
|
url: url,
|
||||||
image: image,
|
image: image,
|
||||||
|
image_avif: image_avif,
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash'
|
||||||
|
|
||||||
import config from './config.mjs'
|
import config from './config.mjs'
|
||||||
import log from './log.mjs'
|
import log from './log.mjs'
|
||||||
import knex from 'knex'
|
import knex from 'knex-core'
|
||||||
|
|
||||||
// This is important for setup to run cleanly.
|
// This is important for setup to run cleanly.
|
||||||
let knexConfig = _.cloneDeep(config.get('knex'))
|
let knexConfig = _.cloneDeep(config.get('knex'))
|
||||||
|
|
|
@ -3,8 +3,6 @@ const storageName = 'logintoken'
|
||||||
const Authentication = {
|
const Authentication = {
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
loadedGoogle: false,
|
|
||||||
loadingGoogle: false,
|
|
||||||
loadingListeners: [],
|
loadingListeners: [],
|
||||||
authListeners: [],
|
authListeners: [],
|
||||||
|
|
||||||
|
@ -32,38 +30,11 @@ const Authentication = {
|
||||||
Authentication.isAdmin = item
|
Authentication.isAdmin = item
|
||||||
},
|
},
|
||||||
|
|
||||||
createGoogleScript: function() {
|
|
||||||
if (Authentication.loadedGoogle) return Promise.resolve()
|
|
||||||
return new Promise(function (res) {
|
|
||||||
if (Authentication.loadedGoogle) return res()
|
|
||||||
Authentication.loadingListeners.push(res)
|
|
||||||
|
|
||||||
if (Authentication.loadingGoogle) return
|
|
||||||
Authentication.loadingGoogle = true
|
|
||||||
|
|
||||||
let gscript = document.createElement('script')
|
|
||||||
gscript.type = 'text/javascript'
|
|
||||||
gscript.async = true
|
|
||||||
gscript.defer = true
|
|
||||||
gscript.src = 'https://apis.google.com/js/platform.js?onload=googleLoaded'
|
|
||||||
document.body.appendChild(gscript)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getToken: function() {
|
getToken: function() {
|
||||||
return localStorage.getItem(storageName)
|
return localStorage.getItem(storageName)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.googleLoaded) {
|
|
||||||
window.googleLoaded = function() {
|
|
||||||
Authentication.loadedGoogle = true
|
|
||||||
while (Authentication.loadingListeners.length) {
|
|
||||||
Authentication.loadingListeners.pop()()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication.updateToken(localStorage.getItem(storageName))
|
Authentication.updateToken(localStorage.getItem(storageName))
|
||||||
|
|
||||||
module.exports = Authentication
|
module.exports = Authentication
|
||||||
|
|
|
@ -90,12 +90,18 @@ const Frontpage = {
|
||||||
if (this.featured && this.featured.banner) {
|
if (this.featured && this.featured.banner) {
|
||||||
var pixelRatio = window.devicePixelRatio || 1
|
var pixelRatio = window.devicePixelRatio || 1
|
||||||
if (deviceWidth < 400 && pixelRatio <= 1) {
|
if (deviceWidth < 400 && pixelRatio <= 1) {
|
||||||
bannerPath = this.featured.banner.small_url
|
bannerPath = window.supportsavif
|
||||||
|
&& this.featured.banner.small_url_avif
|
||||||
|
|| this.featured.banner.small_url
|
||||||
} else if ((deviceWidth < 800 && pixelRatio <= 1)
|
} else if ((deviceWidth < 800 && pixelRatio <= 1)
|
||||||
|| (deviceWidth < 600 && pixelRatio > 1)) {
|
|| (deviceWidth < 600 && pixelRatio > 1)) {
|
||||||
bannerPath = this.featured.banner.medium_url
|
bannerPath = window.supportsavif
|
||||||
|
&& this.featured.banner.medium_url_avif
|
||||||
|
|| this.featured.banner.medium_url
|
||||||
} else {
|
} else {
|
||||||
bannerPath = this.featured.banner.large_url
|
bannerPath = window.supportsavif
|
||||||
|
&& this.featured.banner.large_url_avif
|
||||||
|
|| this.featured.banner.large_url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,15 @@ require('./polyfill')
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
window.m = m
|
window.m = m
|
||||||
|
|
||||||
|
/*
|
||||||
|
* imgsupport.js from leechy/imgsupport
|
||||||
|
*/
|
||||||
|
const AVIF = new Image();
|
||||||
|
AVIF.onload = AVIF.onerror = function () {
|
||||||
|
window.supportsavif = (AVIF.height === 2)
|
||||||
|
}
|
||||||
|
AVIF.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
|
||||||
|
|
||||||
m.route.setOrig = m.route.set
|
m.route.setOrig = m.route.set
|
||||||
m.route.set = function(path, data, options){
|
m.route.set = function(path, data, options){
|
||||||
m.route.setOrig(path, data, options)
|
m.route.setOrig(path, data, options)
|
||||||
|
|
|
@ -3,54 +3,10 @@ const Authentication = require('../authentication')
|
||||||
const Api = require('../api/common')
|
const Api = require('../api/common')
|
||||||
|
|
||||||
const Login = {
|
const Login = {
|
||||||
loadedGoogle: false,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
redirect: '',
|
redirect: '',
|
||||||
error: '',
|
error: '',
|
||||||
|
|
||||||
initGoogleButton: function() {
|
|
||||||
gapi.signin2.render('googlesignin', {
|
|
||||||
'scope': 'email',
|
|
||||||
'width': 240,
|
|
||||||
'height': 50,
|
|
||||||
'longtitle': true,
|
|
||||||
'theme': 'dark',
|
|
||||||
'onsuccess': Login.onGoogleSuccess,
|
|
||||||
'onfailure': Login.onGoogleFailure,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
onGoogleSuccess: function(googleUser) {
|
|
||||||
Login.loading = true
|
|
||||||
m.redraw()
|
|
||||||
|
|
||||||
m.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/login',
|
|
||||||
body: { token: googleUser.Zi.access_token },
|
|
||||||
})
|
|
||||||
.then(function(result) {
|
|
||||||
Authentication.updateToken(result.token)
|
|
||||||
m.route.set(Login.redirect || '/')
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
Login.error = 'Error while logging into NFP! ' + error.status + ': ' + error.message
|
|
||||||
let auth2 = gapi.auth2.getAuthInstance()
|
|
||||||
return auth2.signOut()
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
Login.loading = false
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
onGoogleFailure: function(error) {
|
|
||||||
if (error.error !== 'popup_closed_by_user' && error.error !== 'popup_blocked_by_browser') {
|
|
||||||
Login.error = 'Error while logging into Google: ' + error.error
|
|
||||||
m.redraw()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
Login.redirect = vnode.attrs.redirect || ''
|
Login.redirect = vnode.attrs.redirect || ''
|
||||||
if (Authentication.currentUser) return m.route.set('/')
|
if (Authentication.currentUser) return m.route.set('/')
|
||||||
|
@ -62,10 +18,6 @@ const Login = {
|
||||||
|
|
||||||
oncreate: function() {
|
oncreate: function() {
|
||||||
if (Authentication.currentUser) return
|
if (Authentication.currentUser) return
|
||||||
Authentication.createGoogleScript()
|
|
||||||
.then(function() {
|
|
||||||
Login.initGoogleButton()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loginuser: function(vnode, e) {
|
loginuser: function(vnode, e) {
|
||||||
|
@ -136,8 +88,6 @@ const Login = {
|
||||||
value: 'Login',
|
value: 'Login',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
m('h5', { hidden: Login.loading }, 'Alternative login'),
|
|
||||||
m('div#googlesignin', { hidden: Login.loading }, m('div.loading-spinner')),
|
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -3,23 +3,8 @@ const Authentication = require('../authentication')
|
||||||
|
|
||||||
const Logout = {
|
const Logout = {
|
||||||
oninit: function() {
|
oninit: function() {
|
||||||
Authentication.createGoogleScript()
|
|
||||||
.then(function() {
|
|
||||||
return new Promise(function (res) {
|
|
||||||
gapi.load('auth2', res)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(function() { return gapi.auth2.init() })
|
|
||||||
.then(function() {
|
|
||||||
let auth2 = gapi.auth2.getAuthInstance()
|
|
||||||
return auth2.signOut()
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
Authentication.clearToken()
|
Authentication.clearToken()
|
||||||
m.route.set('/')
|
m.route.set('/')
|
||||||
}, function(err) {
|
|
||||||
console.log('unable to logout:', err)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function() {
|
view: function() {
|
||||||
|
|
|
@ -3,6 +3,8 @@ const Fileinfo = require('./fileinfo')
|
||||||
const Newsitem = {
|
const Newsitem = {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
var pixelRatio = window.devicePixelRatio || 1
|
var pixelRatio = window.devicePixelRatio || 1
|
||||||
|
var jpegImage = pixelRatio > 1 ? vnode.attrs.media.medium_url : vnode.attrs.media.small_url
|
||||||
|
var avifImage = pixelRatio > 1 ? vnode.attrs.media.medium_url_avif : vnode.attrs.media.small_url_avif
|
||||||
return m('newsitem', [
|
return m('newsitem', [
|
||||||
m(m.route.Link,
|
m(m.route.Link,
|
||||||
{ href: '/article/' + vnode.attrs.path, class: 'title' },
|
{ href: '/article/' + vnode.attrs.path, class: 'title' },
|
||||||
|
@ -12,7 +14,14 @@ const Newsitem = {
|
||||||
vnode.attrs.media
|
vnode.attrs.media
|
||||||
? m('a.cover', {
|
? m('a.cover', {
|
||||||
href: '/article/' + vnode.attrs.path,
|
href: '/article/' + vnode.attrs.path,
|
||||||
}, m('img', { alt: 'Image for news item ' + vnode.attrs.name, src: pixelRatio > 1 ? vnode.attrs.media.medium_url : vnode.attrs.media.small_url }))
|
},
|
||||||
|
m('picture', [
|
||||||
|
avifImage ? m('source', {
|
||||||
|
srcset: avifImage,
|
||||||
|
type: 'image/avif',
|
||||||
|
}) : null,
|
||||||
|
m('img', { alt: 'Image for news item ' + vnode.attrs.name, src: jpegImage })
|
||||||
|
]))
|
||||||
: null,
|
: null,
|
||||||
m('div.entrycontent', {
|
m('div.entrycontent', {
|
||||||
class: vnode.attrs.media ? '' : 'extrapadding',
|
class: vnode.attrs.media ? '' : 'extrapadding',
|
||||||
|
|
20
migrations/20210104150910_media.js
Normal file
20
migrations/20210104150910_media.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return Promise.all([
|
||||||
|
knex.schema.table('media', function(table) {
|
||||||
|
table.text('small_image_avif')
|
||||||
|
table.text('medium_image_avif')
|
||||||
|
table.text('large_image_avif')
|
||||||
|
})
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return Promise.all([
|
||||||
|
knex.schema.table('media', function(table) {
|
||||||
|
table.dropColumn('small_image_avif')
|
||||||
|
table.dropColumn('medium_image_avif')
|
||||||
|
table.dropColumn('large_image_avif')
|
||||||
|
})
|
||||||
|
])
|
||||||
|
};
|
11
package.json
11
package.json
|
@ -46,27 +46,26 @@
|
||||||
"homepage": "https://github.com/nfp-projects/nfp_moe",
|
"homepage": "https://github.com/nfp-projects/nfp_moe",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@koa/cors": "^2.2.3",
|
"@koa/cors": "^2.2.3",
|
||||||
"bcrypt": "^3.0.0",
|
"bcrypt": "^3.0.8",
|
||||||
"bookshelf": "^0.15.1",
|
"bookshelf": "^0.15.1",
|
||||||
"bunyan-lite": "^1.0.1",
|
"bunyan-lite": "^1.0.1",
|
||||||
"dot": "^1.1.2",
|
"dot": "^1.1.2",
|
||||||
"format-link-header": "^2.1.0",
|
"format-link-header": "^2.1.0",
|
||||||
"googleapis": "^42.0.0",
|
|
||||||
"http-errors": "^1.7.2",
|
"http-errors": "^1.7.2",
|
||||||
"json-mask": "^0.3.8",
|
"json-mask": "^0.3.8",
|
||||||
"jsonwebtoken": "^8.4.0",
|
"jsonwebtoken": "^8.4.0",
|
||||||
"knex": "^0.16.3",
|
"knex-core": "^0.19.5",
|
||||||
"koa": "^2.7.0",
|
|
||||||
"koa-bodyparser": "^4.2.1",
|
"koa-bodyparser": "^4.2.1",
|
||||||
"koa-jwt": "^3.5.1",
|
"koa-jwt": "^3.5.1",
|
||||||
|
"koa-lite": "^2.10.1",
|
||||||
"koa-router": "^7.4.0",
|
"koa-router": "^7.4.0",
|
||||||
"koa-send": "^5.0.0",
|
"koa-send": "^5.0.0",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"multer": "^1.4.1",
|
"multer": "^1.4.1",
|
||||||
"nconf": "^0.10.0",
|
"nconf-lite": "^1.0.1",
|
||||||
"parse-torrent": "^7.0.1",
|
"parse-torrent": "^7.0.1",
|
||||||
"pg": "^7.8.0",
|
"pg": "^7.8.0",
|
||||||
"sharp": "^0.22.1",
|
"sharp": "^0.27.0",
|
||||||
"striptags": "^3.1.1"
|
"striptags": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
BIN
public/assets/img/heart.png
Normal file
BIN
public/assets/img/heart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 436 KiB |
|
@ -1,4 +1,4 @@
|
||||||
import Koa from 'koa'
|
import Koa from 'koa-lite'
|
||||||
import bodyParser from 'koa-bodyparser'
|
import bodyParser from 'koa-bodyparser'
|
||||||
import cors from '@koa/cors'
|
import cors from '@koa/cors'
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue