diff --git a/package.json b/package.json index 658493068f..8164ee178d 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "underscore.deep": "^0.5.1", "validator": "^6.1.0", "winston": "^2.1.0", + "xml": "^1.0.1", "xregexp": "~3.1.0" }, "devDependencies": { diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 12ebff1540..913dbb6fd6 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -99,10 +99,11 @@ $(document).ready(function () { }; ajaxify.handleRedirects = function (url) { - url = ajaxify.removeRelativePath(url.replace(/\/$/, '')).toLowerCase(); + url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, '')).toLowerCase(); var isClientToAdmin = url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') !== 0; var isAdminToClient = !url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') === 0; - var uploadsOrApi = url.startsWith('uploads') || url.startsWith('api'); + var uploadsOrApi = url.startsWith('assets/uploads') || url.startsWith('uploads') || url.startsWith('api'); + if (isClientToAdmin || isAdminToClient || uploadsOrApi) { window.open(RELATIVE_PATH + '/' + url, '_top'); return true; diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index eba41faf9c..bffefc725f 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -49,9 +49,12 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds' function changePageSkin(skinName) { var css = $('#bootswatchCSS'); - if (skinName === 'default') { + if (skinName === 'noskin' || (skinName === 'default' && config.defaultBootswatchSkin === 'noskin')) { css.remove(); } else { + if (skinName === 'default') { + skinName = config.defaultBootswatchSkin; + } var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinName + '/bootstrap.min.css'; if (css.length) { css.attr('href', cssSource); diff --git a/public/src/modules/pictureCropper.js b/public/src/modules/pictureCropper.js index 0ed62d3d8c..bbe0a76735 100644 --- a/public/src/modules/pictureCropper.js +++ b/public/src/modules/pictureCropper.js @@ -42,6 +42,7 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe var img = document.getElementById('cropped-image'); var cropperTool = new cropper.default(img, { aspectRatio: data.aspectRatio, + autoCropArea: 1, viewMode: 1, cropmove: function () { if (data.restrictImageDimension) { diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index edd5d58282..2f360f883c 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -108,6 +108,7 @@ settingsController.get = function (req, res, callback) { userData.bootswatchSkinOptions = [ + { name: 'No skin', value: 'noskin' }, { name: 'Default', value: 'default' }, { name: 'Cerulean', value: 'cerulean' }, { name: 'Cosmo', value: 'cosmo' }, diff --git a/src/controllers/api.js b/src/controllers/api.js index cfa78a6908..5ddbd0731b 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -61,7 +61,8 @@ apiController.getConfig = function (req, res, next) { config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest'; config.csrf_token = req.csrfToken(); config.searchEnabled = plugins.hasListeners('filter:search.query'); - config.bootswatchSkin = meta.config.bootswatchSkin || 'default'; + config.bootswatchSkin = meta.config.bootswatchSkin || 'noskin'; + config.defaultBootswatchSkin = meta.config.bootswatchSkin || 'noskin'; var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff; config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff; @@ -90,7 +91,7 @@ apiController.getConfig = function (req, res, next) { config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort; config.topicSearchEnabled = settings.topicSearchEnabled || false; config.delayImageLoading = settings.delayImageLoading !== undefined ? settings.delayImageLoading : true; - config.bootswatchSkin = settings.bootswatchSkin || config.bootswatchSkin; + config.bootswatchSkin = (settings.bootswatchSkin && settings.bootswatchSkin !== 'default') ? settings.bootswatchSkin : config.bootswatchSkin; plugins.fireHook('filter:config.get', config, next); }, ], function (err, config) { diff --git a/src/controllers/index.js b/src/controllers/index.js index 49c1c4c2c2..93b8e9a383 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -30,6 +30,7 @@ Controllers.admin = require('./admin'); Controllers.globalMods = require('./globalmods'); Controllers.mods = require('./mods'); Controllers.sitemap = require('./sitemap'); +Controllers.osd = require('./osd'); Controllers['404'] = require('./404'); Controllers.errors = require('./errors'); @@ -130,7 +131,7 @@ Controllers.login = function (req, res, next) { if (!data.allowLocalLogin && !data.allowRegistration && data.alternate_logins && data.authentication.length === 1) { if (res.locals.isAPI) { return helpers.redirect(res, { - external: data.authentication[0].url, + external: nconf.get('relative_path') + data.authentication[0].url, }); } return res.redirect(nconf.get('relative_path') + data.authentication[0].url); diff --git a/src/controllers/osd.js b/src/controllers/osd.js new file mode 100644 index 0000000000..c83f7f142c --- /dev/null +++ b/src/controllers/osd.js @@ -0,0 +1,32 @@ +'use strict'; + +var xml = require('xml'); +var nconf = require('nconf'); + +var plugins = require('../plugins'); +var meta = require('../meta'); + +module.exports.handle = function (req, res, next) { + if (plugins.hasListeners('filter:search.query')) { + res.type('application/xml').send(generateXML()); + } else { + next(); + } +}; + +function generateXML() { + return xml([{ + OpenSearchDescription: [ + { _attr: { xmlns: 'http://a9.com/-/spec/opensearch/1.1/' } }, + { ShortName: String(meta.config.title || meta.config.browserTitle || 'NodeBB') }, + { Description: String(meta.config.description || '') }, + { Url: { + _attr: { + type: 'text/html', + method: 'get', + template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', + }, + } }, + ], + }], { declaration: true, indent: '\t' }); +} diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 7a309d5200..82556ee12c 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -122,7 +122,7 @@ function resizeImage(fileObj, callback) { var extname = path.extname(fileObj.url); var basename = path.basename(fileObj.url, extname); - fileObj.url = path.join(dirname, basename + '-resized' + extname); + fileObj.url = dirname + '/' + basename + '-resized' + extname; next(null, fileObj); }, diff --git a/src/meta/tags.js b/src/meta/tags.js index fa88fa30b8..5b1097d427 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -61,6 +61,14 @@ module.exports = function (Meta) { href: nconf.get('relative_path') + '/manifest.json', }]; + if (plugins.hasListeners('filter:search.query')) { + defaultLinks.push({ + rel: 'search', + type: 'application/opensearchdescription+xml', + href: nconf.get('relative_path') + '/osd.xml', + }); + } + // Touch icons for mobile-devices if (Meta.config['brand:touchIcon']) { defaultLinks.push({ diff --git a/src/middleware/header.js b/src/middleware/header.js index 44a541f4bf..e5d4de66bb 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -190,12 +190,12 @@ module.exports = function (middleware) { } function setBootswatchCSS(obj, config) { - if (config && config.bootswatchSkin !== 'default') { + if (config && config.bootswatchSkin !== 'noskin') { var skinToUse = ''; if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1) { skinToUse = config.bootswatchSkin; - } else if (meta.config.bootswatchSkin !== 'default') { + } else if (meta.config.bootswatchSkin) { skinToUse = meta.config.bootswatchSkin; } diff --git a/src/routes/meta.js b/src/routes/meta.js index cfeeac5b9b..de0bb52406 100644 --- a/src/routes/meta.js +++ b/src/routes/meta.js @@ -8,4 +8,5 @@ module.exports = function (app, middleware, controllers) { app.get('/robots.txt', controllers.robots); app.get('/manifest.json', controllers.manifest); app.get('/css/previews/:theme', controllers.admin.themes.get); + app.get('/osd.xml', controllers.osd.handle); };