diff --git a/public/language/en-GB/admin/settings/general.json b/public/language/en-GB/admin/settings/general.json index d56c819745..0ee921d831 100644 --- a/public/language/en-GB/admin/settings/general.json +++ b/public/language/en-GB/admin/settings/general.json @@ -18,7 +18,7 @@ "description": "Site Description", "keywords": "Site Keywords", "keywords-placeholder": "Keywords describing your community, comma-separated", - "logo-and-icons": "Site Logo & Icons", + "logo-and-icons": "Media & Branding", "logo.image": "Image", "logo.image-placeholder": "Path to a logo to display on forum header", "logo.upload": "Upload", @@ -35,6 +35,8 @@ "touch-icon.help": "Recommended size and format: 512x512, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.", "maskable-icon": "Maskable (Homescreen) Icon", "maskable-icon.help": "Recommended size and format: 512x512, PNG format only. If no maskable icon is specified, NodeBB will fall back to the Touch Icon.", + "screenshot": "Screenshot", + "screenshot.help": "Recommended size and format: between 320px and 3480px, JPG and PNG format only. If no screenshot is specified, NodeBB will fall back to a generic screenshot", "outgoing-links": "Outgoing Links", "outgoing-links.warning-page": "Use Outgoing Links Warning Page", "search": "Search", diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index cd9463fce2..21be9c441b 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -198,7 +198,6 @@ uploadsController.uploadTouchIcon = async function (req, res, next) { } }; - uploadsController.uploadMaskableIcon = async function (req, res, next) { const uploadedFile = req.files[0]; const allowedTypes = ['image/png']; @@ -214,6 +213,21 @@ uploadsController.uploadMaskableIcon = async function (req, res, next) { } }; +uploadsController.uploadScreenshot = async function (req, res, next) { + const uploadedFile = req.files[0]; + const allowedTypes = ['image/png', 'image/jpeg']; + + await validateUpload(uploadedFile, allowedTypes); + try { + const imageObj = await file.saveFileToLocal('screenshot.png', 'system', uploadedFile.path); + res.json([{ name: uploadedFile.name, url: imageObj.url }]); + } catch (err) { + next(err); + } finally { + file.delete(uploadedFile.path); + } +}; + uploadsController.uploadFile = async function (req, res, next) { const uploadedFile = req.files[0]; let params; diff --git a/src/controllers/index.js b/src/controllers/index.js index 0336db8d1d..2e55cfe646 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -1,11 +1,14 @@ 'use strict'; +const path = require('path'); const nconf = require('nconf'); const validator = require('validator'); +const mime = require('mime'); const meta = require('../meta'); const user = require('../user'); const plugins = require('../plugins'); +const image = require('../image'); const privilegesHelpers = require('../privileges/helpers'); const helpers = require('./helpers'); @@ -271,6 +274,7 @@ Controllers.manifest = async function (req, res) { const manifest = { name: meta.config.title || 'NodeBB', short_name: meta.config['title:short'] || meta.config.title || 'NodeBB', + ...(meta.config.description && { description: meta.config.description }), start_url: nconf.get('url'), display: 'standalone', orientation: 'portrait', @@ -279,6 +283,33 @@ Controllers.manifest = async function (req, res) { icons: [], }; + if (meta.config['brand:screenshot']) { + let sizes; + try { + const { width, height } = await image.size(path.join(nconf.get('base_dir'), meta.config['brand:screenshot'].replace('assets', 'public'))); + sizes = `${width}x${height}`; + } catch (e) { + // noop + } + manifest.screenshots = [ + { + src: `${nconf.get('relative_path')}${meta.config['brand:screenshot']}`, + ...(sizes && { sizes }), + type: mime.getType(meta.config['brand:screenshot']), + }, + ]; + } else { + manifest.screenshots = [ + { + src: `${nconf.get('relative_path')}/assets/images/screenshot-default.png`, + sizes: '446x778', + type: 'image/png', + form_factor: 'narrow', + label: 'Default home page of a vanilla NodeBB installation.', + }, + ]; + } + if (meta.config['brand:touchIcon']) { manifest.icons.push({ src: `${nconf.get('relative_path')}/assets/uploads/system/touchicon-36.png`, @@ -316,6 +347,43 @@ Controllers.manifest = async function (req, res) { type: 'image/png', density: 10.0, }); + } else { + manifest.icons.push({ + src: `${nconf.get('relative_path')}/assets/images/touch/36.png`, + sizes: '36x36', + type: 'image/png', + density: 0.75, + }, { + src: `${nconf.get('relative_path')}/assets/images/touch/48.png`, + sizes: '48x48', + type: 'image/png', + density: 1.0, + }, { + src: `${nconf.get('relative_path')}/assets/images/touch/72.png`, + sizes: '72x72', + type: 'image/png', + density: 1.5, + }, { + src: `${nconf.get('relative_path')}/assets/images/touch/96.png`, + sizes: '96x96', + type: 'image/png', + density: 2.0, + }, { + src: `${nconf.get('relative_path')}/assets/images/touch/144.png`, + sizes: '144x144', + type: 'image/png', + density: 3.0, + }, { + src: `${nconf.get('relative_path')}/assets/images/touch/192.png`, + sizes: '192x192', + type: 'image/png', + density: 4.0, + }, { + src: `${nconf.get('relative_path')}/assets/images/touch/512.png`, + sizes: '512x512', + type: 'image/png', + density: 10.0, + }); } diff --git a/src/privileges/admin.js b/src/privileges/admin.js index ae26ab069a..8e40029786 100644 --- a/src/privileges/admin.js +++ b/src/privileges/admin.js @@ -67,6 +67,7 @@ privsAdmin.routeMap = { uploadfavicon: 'admin:settings', uploadTouchIcon: 'admin:settings', uploadMaskableIcon: 'admin:settings', + uploadScreenshot: 'admin:settings', uploadlogo: 'admin:settings', uploadOgImage: 'admin:settings', uploadDefaultAvatar: 'admin:settings', diff --git a/src/routes/admin.js b/src/routes/admin.js index 86debb305d..768e457409 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -106,6 +106,7 @@ function apiRoutes(router, name, middleware, controllers) { router.post(`/api/${name}/uploadfavicon`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadFavicon)); router.post(`/api/${name}/uploadTouchIcon`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadTouchIcon)); router.post(`/api/${name}/uploadMaskableIcon`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadMaskableIcon)); + router.post(`/api/${name}/uploadScreenshot`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadScreenshot)); router.post(`/api/${name}/uploadlogo`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadLogo)); router.post(`/api/${name}/uploadOgImage`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadOgImage)); router.post(`/api/${name}/upload/file`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadFile)); diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl index 5dccda3f0a..a5c5289104 100644 --- a/src/views/admin/settings/general.tpl +++ b/src/views/admin/settings/general.tpl @@ -153,6 +153,19 @@ [[admin/settings/general:maskable-icon.help]]

+ +
+ +
+ + + + +
+

+ [[admin/settings/general:screenshot.help]] +

+