feat: upload site favicon

pull/5698/head
Nicolas Giard 2 years ago
parent 33547ddce9
commit 9a4ac686ca
No known key found for this signature in database
GPG Key ID: 85061B8F9D55B7C8

@ -51,6 +51,14 @@ router.get('/_site/:siteId?/:resource', async (req, res, next) => {
} }
break break
} }
case 'favicon': {
if (site.config.assets.favicon) {
res.sendFile(path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`))
} else {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
}
break
}
default: { default: {
return res.status(404).send('Invalid Site Resource') return res.status(404).send('Invalid Site Resource')
} }

@ -488,7 +488,8 @@ exports.up = async knex => {
assets: { assets: {
logo: false, logo: false,
logoExt: 'svg', logoExt: 'svg',
favicon: false favicon: false,
faviconExt: 'svg'
}, },
theme: { theme: {
dark: false, dark: false,

@ -156,6 +156,9 @@ module.exports = {
if (!WIKI.extensions.ext.sharp.isInstalled) { if (!WIKI.extensions.ext.sharp.isInstalled) {
throw new Error('This feature requires the Sharp extension but it is not installed.') throw new Error('This feature requires the Sharp extension but it is not installed.')
} }
if (!['.svg', '.png', '.jpg', 'webp', '.gif'].some(s => filename.endsWith(s))) {
throw new Error('Invalid File Extension. Must be svg, png, jpg, webp or gif.')
}
const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png' const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
const destFolder = path.resolve( const destFolder = path.resolve(
process.cwd(), process.cwd(),
@ -198,10 +201,52 @@ module.exports = {
* UPLOAD FAVICON * UPLOAD FAVICON
*/ */
async uploadSiteFavicon (obj, args) { async uploadSiteFavicon (obj, args) {
const { filename, mimetype, createReadStream } = await args.image try {
console.info(filename, mimetype) const { filename, mimetype, createReadStream } = await args.image
return { WIKI.logger.info(`Processing site favicon ${filename} of type ${mimetype}...`)
operation: graphHelper.generateSuccess('Site favicon uploaded successfully') if (!WIKI.extensions.ext.sharp.isInstalled) {
throw new Error('This feature requires the Sharp extension but it is not installed.')
}
if (!['.svg', '.png', '.jpg', '.webp', '.gif'].some(s => filename.endsWith(s))) {
throw new Error('Invalid File Extension. Must be svg, png, jpg, webp or gif.')
}
const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
const destFolder = path.resolve(
process.cwd(),
WIKI.config.dataPath,
`assets`
)
const destPath = path.join(destFolder, `favicon-${args.id}.${destFormat}`)
await fs.ensureDir(destFolder)
// -> Resize
await WIKI.extensions.ext.sharp.resize({
format: destFormat,
inputStream: createReadStream(),
outputPath: destPath,
width: 64,
height: 64
})
// -> Save favicon meta to DB
const site = await WIKI.models.sites.query().findById(args.id)
if (!site.config.assets.favicon) {
site.config.assets.favicon = uuid()
}
site.config.assets.faviconExt = destFormat
await WIKI.models.sites.query().findById(args.id).patch({ config: site.config })
await WIKI.models.sites.reloadCache()
// -> Save image data to DB
const imgBuffer = await fs.readFile(destPath)
await WIKI.models.knex('assetData').insert({
id: site.config.assets.favicon,
data: imgBuffer
}).onConflict('id').merge()
WIKI.logger.info('New site favicon processed successfully.')
return {
operation: graphHelper.generateSuccess('Site favicon uploaded successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
} }
} }
} }

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/_site/favicon" />
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no"> <meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

@ -77,6 +77,12 @@ module.exports = configure(function (/* ctx */) {
extendViteConf (viteConf) { extendViteConf (viteConf) {
viteConf.build.assetsDir = '_assets' viteConf.build.assetsDir = '_assets'
viteConf.build.rollupOptions = {
...viteConf.build.rollupOptions ?? {},
external: [
/^\/_site\//
]
}
}, },
// viteVuePluginOptions: {}, // viteVuePluginOptions: {},

@ -173,14 +173,14 @@
"admin.general.displaySiteTitle": "Display Site Title", "admin.general.displaySiteTitle": "Display Site Title",
"admin.general.displaySiteTitleHint": "Should the site title be displayed next to the logo? If your logo isn't square and contain your brand name, turn this option off.", "admin.general.displaySiteTitleHint": "Should the site title be displayed next to the logo? If your logo isn't square and contain your brand name, turn this option off.",
"admin.general.favicon": "Favicon", "admin.general.favicon": "Favicon",
"admin.general.faviconHint": "Favicon image file, in SVG, PNG, ICO or GIF format. Must be a square image.", "admin.general.faviconHint": "Favicon image file, in SVG, PNG, JPG, WEBP or GIF format. Must be a square image.",
"admin.general.faviconUploadSuccess": "Site Favicon uploaded successfully.", "admin.general.faviconUploadSuccess": "Site Favicon uploaded successfully.",
"admin.general.features": "Features", "admin.general.features": "Features",
"admin.general.footerCopyright": "Footer / Copyright", "admin.general.footerCopyright": "Footer / Copyright",
"admin.general.general": "General", "admin.general.general": "General",
"admin.general.logo": "Logo", "admin.general.logo": "Logo",
"admin.general.logoUpl": "Site Logo", "admin.general.logoUpl": "Site Logo",
"admin.general.logoUplHint": "Logo image file, in SVG, PNG, JPG or GIF format.", "admin.general.logoUplHint": "Logo image file, in SVG, PNG, JPG, WEBP or GIF format.",
"admin.general.logoUploadSuccess": "Site logo uploaded successfully.", "admin.general.logoUploadSuccess": "Site logo uploaded successfully.",
"admin.general.ratingsOff": "Off", "admin.general.ratingsOff": "Off",
"admin.general.ratingsStars": "Stars", "admin.general.ratingsStars": "Stars",

@ -302,10 +302,11 @@ q-page.admin-general
.admin-general-favicontabs.q-mt-md .admin-general-favicontabs.q-mt-md
div div
q-avatar( q-avatar(
v-if='adminStore.currentSiteId'
size='24px' size='24px'
square square
) )
img(src='/_assets/logo-wikijs.svg') img(:src='`/_site/` + adminStore.currentSiteId + `/favicon?` + state.assetTimestamp')
.text-caption.q-ml-sm {{state.config.title}} .text-caption.q-ml-sm {{state.config.title}}
div div
q-icon(name='las la-otter', size='24px', color='grey') q-icon(name='las la-otter', size='24px', color='grey')
@ -669,7 +670,7 @@ async function uploadLogo () {
input.onchange = async e => { input.onchange = async e => {
state.loading++ state.loading++
try { try {
await APOLLO_CLIENT.mutate({ const resp = await APOLLO_CLIENT.mutate({
mutation: gql` mutation: gql`
mutation uploadLogo ( mutation uploadLogo (
$id: UUID! $id: UUID!
@ -681,7 +682,6 @@ async function uploadLogo () {
) { ) {
operation { operation {
succeeded succeeded
slug
message message
} }
} }
@ -692,11 +692,15 @@ async function uploadLogo () {
image: e.target.files[0] image: e.target.files[0]
} }
}) })
$q.notify({ if (resp?.data?.uploadSiteLogo?.operation?.succeeded) {
type: 'positive', $q.notify({
message: t('admin.general.logoUploadSuccess') type: 'positive',
}) message: t('admin.general.logoUploadSuccess')
state.assetTimestamp = (new Date()).toISOString() })
state.assetTimestamp = (new Date()).toISOString()
} else {
throw new Error(resp?.data?.uploadSiteLogo?.operation?.message || 'An unexpected error occured.')
}
} catch (err) { } catch (err) {
$q.notify({ $q.notify({
type: 'negative', type: 'negative',
@ -717,7 +721,7 @@ async function uploadFavicon () {
input.onchange = async e => { input.onchange = async e => {
state.loading++ state.loading++
try { try {
await APOLLO_CLIENT.mutate({ const resp = await APOLLO_CLIENT.mutate({
mutation: gql` mutation: gql`
mutation uploadFavicon ( mutation uploadFavicon (
$id: UUID! $id: UUID!
@ -727,9 +731,8 @@ async function uploadFavicon () {
id: $id id: $id
image: $image image: $image
) { ) {
status { operation {
succeeded succeeded
slug
message message
} }
} }
@ -740,10 +743,15 @@ async function uploadFavicon () {
image: e.target.files[0] image: e.target.files[0]
} }
}) })
$q.notify({ if (resp?.data?.uploadSiteFavicon?.operation?.succeeded) {
type: 'positive', $q.notify({
message: t('admin.general.faviconUploadSuccess') type: 'positive',
}) message: t('admin.general.faviconUploadSuccess')
})
state.assetTimestamp = (new Date()).toISOString()
} else {
throw new Error(resp?.data?.uploadSiteFavicon?.operation?.message || 'An unexpected error occured.')
}
} catch (err) { } catch (err) {
$q.notify({ $q.notify({
type: 'negative', type: 'negative',

@ -119,7 +119,7 @@ onMounted(() => {
state.connecting = true state.connecting = true
// socket = io(window.location.host, { // socket = io(window.location.host, {
socket = io('localhost:3000', { socket = io(window.location.host, {
path: '/_ws/', path: '/_ws/',
auth: { auth: {
token: 'TEST' // TODO: Use active token token: 'TEST' // TODO: Use active token

Loading…
Cancel
Save