mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 11:35:55 +01:00
Add new ACP option to upload Touch Icon, #3668
Also added a number of fixes for mobile enhancements, such as serving a manifest.json file for Android devices, and serving proper link tags for all uploaded touch icons. This commit also creates a new template helper for link tags.
This commit is contained in:
@@ -8,6 +8,12 @@ define('admin/settings/general', ['admin/settings'], function(Settings) {
|
||||
$('button[data-action="removeLogo"]').on('click', function() {
|
||||
$('input[data-field="brand:logo"]').val('');
|
||||
});
|
||||
$('button[data-action="removeFavicon"]').on('click', function() {
|
||||
$('input[data-field="brand:favicon"]').val('');
|
||||
});
|
||||
$('button[data-action="removeTouchIcon"]').on('click', function() {
|
||||
$('input[data-field="brand:touchIcon"]').val('');
|
||||
});
|
||||
};
|
||||
|
||||
return Module;
|
||||
|
||||
@@ -40,6 +40,16 @@
|
||||
return '<meta ' + name + property + content + '/>';
|
||||
};
|
||||
|
||||
helpers.buildLinkTag = function(tag) {
|
||||
var link = tag.link ? 'link="' + tag.link + '" ' : '',
|
||||
rel = tag.rel ? 'rel="' + tag.rel + '" ' : '',
|
||||
type = tag.type ? 'type="' + tag.type + '" ' : '',
|
||||
href = tag.href ? 'href="' + tag.href + '" ' : '',
|
||||
sizes = tag.sizes ? 'sizes="' + tag.sizes + '" ' : '';
|
||||
|
||||
return '<link ' + link + rel + type + sizes + href + '/>';
|
||||
};
|
||||
|
||||
helpers.stringify = function(obj) {
|
||||
// Turns the incoming object into a JSON string
|
||||
return JSON.stringify(obj).replace(/&/gm,"&").replace(/</gm,"<").replace(/>/gm,">").replace(/"/g, '"');
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
async = require('async'),
|
||||
nconf = require('nconf'),
|
||||
winston = require('winston'),
|
||||
file = require('../../file'),
|
||||
image = require('../../image'),
|
||||
plugins = require('../../plugins');
|
||||
|
||||
|
||||
@@ -52,6 +54,41 @@ uploadsController.uploadFavicon = function(req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
uploadsController.uploadTouchIcon = function(req, res, next) {
|
||||
var uploadedFile = req.files.files[0],
|
||||
allowedTypes = ['image/png'],
|
||||
sizes = [36, 48, 72, 96, 144, 192];
|
||||
|
||||
if (validateUpload(req, res, next, uploadedFile, allowedTypes)) {
|
||||
file.saveFileToLocal('touchicon-orig.png', 'system', uploadedFile.path, function(err, imageObj) {
|
||||
// Resize the image into squares for use as touch icons at various DPIs
|
||||
async.each(sizes, function(size, next) {
|
||||
async.series([
|
||||
async.apply(file.saveFileToLocal, 'touchicon-' + size + '.png', 'system', uploadedFile.path),
|
||||
async.apply(image.resizeImage, {
|
||||
path: path.join(nconf.get('base_dir'), nconf.get('upload_path'), 'system', 'touchicon-' + size + '.png'),
|
||||
extension: 'png',
|
||||
width: size,
|
||||
height: size
|
||||
})
|
||||
], next);
|
||||
}, function(err) {
|
||||
fs.unlink(uploadedFile.path, function(err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json([{name: uploadedFile.name, url: imageObj.url}]);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
uploadsController.uploadLogo = function(req, res, next) {
|
||||
upload('site-logo', req, res, next);
|
||||
};
|
||||
|
||||
@@ -186,6 +186,52 @@ Controllers.robots = function (req, res) {
|
||||
}
|
||||
};
|
||||
|
||||
Controllers.manifest = function(req, res) {
|
||||
var manifest = {
|
||||
name: meta.config.title || 'NodeBB',
|
||||
start_url: nconf.get('relative_path') + '/',
|
||||
display: 'standalone',
|
||||
orientation: 'portrait',
|
||||
icons: []
|
||||
};
|
||||
|
||||
if (meta.config['brand:touchIcon']) {
|
||||
manifest.icons.push({
|
||||
src: nconf.get('relative_path') + '/uploads/system/touchicon-36.png',
|
||||
sizes: '36x36',
|
||||
type: 'image/png',
|
||||
density: 0.75
|
||||
}, {
|
||||
src: nconf.get('relative_path') + '/uploads/system/touchicon-48.png',
|
||||
sizes: '48x48',
|
||||
type: 'image/png',
|
||||
density: 1.0
|
||||
}, {
|
||||
src: nconf.get('relative_path') + '/uploads/system/touchicon-72.png',
|
||||
sizes: '72x72',
|
||||
type: 'image/png',
|
||||
density: 1.5
|
||||
}, {
|
||||
src: nconf.get('relative_path') + '/uploads/system/touchicon-96.png',
|
||||
sizes: '96x96',
|
||||
type: 'image/png',
|
||||
density: 2.0
|
||||
}, {
|
||||
src: nconf.get('relative_path') + '/uploads/system/touchicon-144.png',
|
||||
sizes: '144x144',
|
||||
type: 'image/png',
|
||||
density: 3.0
|
||||
}, {
|
||||
src: nconf.get('relative_path') + '/uploads/system/touchicon-192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
density: 4.0
|
||||
})
|
||||
}
|
||||
|
||||
res.status(200).json(manifest);
|
||||
};
|
||||
|
||||
Controllers.outgoing = function(req, res, next) {
|
||||
var url = req.query.url,
|
||||
data = {
|
||||
|
||||
@@ -74,7 +74,12 @@ uploadsController.uploadThumb = function(req, res, next) {
|
||||
|
||||
if (uploadedFile.type.match(/image./)) {
|
||||
var size = meta.config.topicThumbSize || 120;
|
||||
image.resizeImage(uploadedFile.path, path.extname(uploadedFile.name), size, size, function(err) {
|
||||
image.resizeImage({
|
||||
path: uploadedFile.path,
|
||||
extension: path.extname(uploadedFile.name),
|
||||
width: size,
|
||||
height: size
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
18
src/image.js
18
src/image.js
@@ -7,18 +7,18 @@ var fs = require('fs'),
|
||||
|
||||
var image = {};
|
||||
|
||||
image.resizeImage = function(path, extension, width, height, callback) {
|
||||
image.resizeImage = function(data, callback) {
|
||||
if (plugins.hasListeners('filter:image.resize')) {
|
||||
plugins.fireHook('filter:image.resize', {
|
||||
path: path,
|
||||
extension: extension,
|
||||
width: width,
|
||||
height: height
|
||||
path: data.path,
|
||||
extension: data.extension,
|
||||
width: data.width,
|
||||
height: data.height
|
||||
}, function(err, data) {
|
||||
callback(err);
|
||||
});
|
||||
} else {
|
||||
new Jimp(path, function(err, image) {
|
||||
new Jimp(data.path, function(err, image) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ image.resizeImage = function(path, extension, width, height, callback) {
|
||||
var w = image.bitmap.width,
|
||||
h = image.bitmap.height,
|
||||
origRatio = w/h,
|
||||
desiredRatio = width/height,
|
||||
desiredRatio = data.width/data.height,
|
||||
x = 0,
|
||||
y = 0,
|
||||
crop;
|
||||
@@ -47,10 +47,10 @@ image.resizeImage = function(path, extension, width, height, callback) {
|
||||
async.waterfall([
|
||||
crop,
|
||||
function(image, next) {
|
||||
image.resize(width, height, next);
|
||||
image.resize(data.width, data.height, next);
|
||||
},
|
||||
function(image, next) {
|
||||
image.write(path, next);
|
||||
image.write(data.target || data.path, next);
|
||||
}
|
||||
], function(err) {
|
||||
callback(err);
|
||||
|
||||
@@ -21,6 +21,9 @@ module.exports = function(Meta) {
|
||||
}, {
|
||||
name: 'apple-mobile-web-app-capable',
|
||||
content: 'yes'
|
||||
}, {
|
||||
name: 'mobile-web-app-capable',
|
||||
content: 'yes'
|
||||
}, {
|
||||
property: 'og:site_name',
|
||||
content: Meta.config.title || 'NodeBB'
|
||||
@@ -42,9 +45,41 @@ module.exports = function(Meta) {
|
||||
type: "image/x-icon",
|
||||
href: nconf.get('relative_path') + '/favicon.ico'
|
||||
}, {
|
||||
rel: 'apple-touch-icon',
|
||||
href: nconf.get('relative_path') + '/apple-touch-icon'
|
||||
rel: "manifest",
|
||||
href: nconf.get('relative_path') + '/manifest.json'
|
||||
}];
|
||||
|
||||
// Touch icons for mobile-devices
|
||||
if (Meta.config['brand:touchIcon']) {
|
||||
defaultLinks.push({
|
||||
rel: 'apple-touch-icon',
|
||||
href: nconf.get('relative_path') + '/apple-touch-icon'
|
||||
}, {
|
||||
rel: 'icon',
|
||||
sizes: '36x36',
|
||||
href: nconf.get('relative_path') + '/uploads/system/touchicon-36.png'
|
||||
}, {
|
||||
rel: 'icon',
|
||||
sizes: '48x48',
|
||||
href: nconf.get('relative_path') + '/uploads/system/touchicon-48.png'
|
||||
}, {
|
||||
rel: 'icon',
|
||||
sizes: '72x72',
|
||||
href: nconf.get('relative_path') + '/uploads/system/touchicon-72.png'
|
||||
}, {
|
||||
rel: 'icon',
|
||||
sizes: '96x96',
|
||||
href: nconf.get('relative_path') + '/uploads/system/touchicon-96.png'
|
||||
}, {
|
||||
rel: 'icon',
|
||||
sizes: '144x144',
|
||||
href: nconf.get('relative_path') + '/uploads/system/touchicon-144.png'
|
||||
}, {
|
||||
rel: 'icon',
|
||||
sizes: '192x192',
|
||||
href: nconf.get('relative_path') + '/uploads/system/touchicon-192.png'
|
||||
});
|
||||
}
|
||||
plugins.fireHook('filter:meta.getLinkTags', defaultLinks, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
|
||||
@@ -13,6 +13,7 @@ function apiRoutes(router, middleware, controllers) {
|
||||
|
||||
router.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
|
||||
router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
|
||||
router.post('/uploadTouchIcon', middlewares, controllers.admin.uploads.uploadTouchIcon);
|
||||
router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
|
||||
router.post('/uploadgravatardefault', middlewares, controllers.admin.uploads.uploadGravatarDefault);
|
||||
}
|
||||
|
||||
@@ -33,5 +33,6 @@ module.exports = function(app, middleware, controllers) {
|
||||
// app.get('/nodebb.min.js.map', middleware.addExpiresHeaders, sendJSSourceMap);
|
||||
app.get('/sitemap.xml', controllers.sitemap);
|
||||
app.get('/robots.txt', controllers.robots);
|
||||
app.get('/manifest.json', controllers.manifest);
|
||||
app.get('/css/previews/:theme', controllers.admin.themes.get);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,12 @@ module.exports = function(User) {
|
||||
file.isFileTypeAllowed(picture.path, ['png', 'jpeg', 'jpg', 'gif'], next);
|
||||
},
|
||||
function(next) {
|
||||
image.resizeImage(picture.path, extension, imageDimension, imageDimension, next);
|
||||
image.resizeImage({
|
||||
path: picture.path,
|
||||
extension: extension,
|
||||
width: imageDimension,
|
||||
height: imageDimension
|
||||
}, next);
|
||||
},
|
||||
function(next) {
|
||||
if (convertToPNG) {
|
||||
|
||||
@@ -74,12 +74,33 @@
|
||||
<input id="faviconUrl" type="text" class="form-control" placeholder="favicon.ico" data-field="brand:favicon" data-action="upload" data-target="faviconUrl" data-route="{config.relative_path}/api/admin/uploadfavicon" readonly />
|
||||
<span class="input-group-btn">
|
||||
<input data-action="upload" data-target="faviconUrl" data-route="{config.relative_path}/api/admin/uploadfavicon" type="button" class="btn btn-default" value="Upload"></input>
|
||||
<button data-action="removeFavicon" type="button" class="btn btn-default btn-danger"><i class="fa fa-times"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">
|
||||
Homescreen/Touch Icon
|
||||
</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input id="touchIconUrl" type="text" class="form-control" data-field="brand:touchIcon" data-action="upload" data-target="touchIconUrl" data-route="{config.relative_path}/api/admin/uploadTouchIcon" readonly />
|
||||
<span class="input-group-btn">
|
||||
<input data-action="upload" data-target="touchIconUrl" data-route="{config.relative_path}/api/admin/uploadTouchIcon" type="button" class="btn btn-default" value="Upload"></input>
|
||||
<button data-action="removeTouchIcon" type="button" class="btn btn-default btn-danger"><i class="fa fa-times"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Recommended size and format: 192x192, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">Miscellaneous</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
|
||||
Reference in New Issue
Block a user