From c75391f9f5eafca3c50f6483a0123e6707d89acf Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Thu, 16 Feb 2017 22:56:25 -0700 Subject: [PATCH 01/22] Sound system improvements - Fix sound ACP uploads - Display soundpack names with sound files - Soundpacks can have sounds with identical names - Link sounds during build step - Generate map of sound name to file name during build step - Change how soundpacks work. It's now done via a field in plugin.json --- package.json | 2 +- public/src/admin/general/sounds.js | 8 +- public/src/client/account/settings.js | 6 +- public/src/modules/chat.js | 4 +- public/src/modules/notifications.js | 4 +- public/src/modules/sounds.js | 113 +++++++-------- src/controllers/accounts/settings.js | 57 ++++++-- src/controllers/admin/sounds.js | 40 +++-- src/controllers/admin/uploads.js | 9 +- src/meta/build.js | 14 +- src/meta/sounds.js | 201 +++++++++++--------------- src/plugins.js | 1 + src/plugins/load.js | 42 +++++- src/socket.io/modules.js | 16 +- src/views/admin/general/sounds.tpl | 46 ++++-- src/webserver.js | 6 +- 16 files changed, 321 insertions(+), 248 deletions(-) diff --git a/package.json b/package.json index 2e16cc0e19..e9d40fe546 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "nodebb-plugin-emoji-one": "1.1.5", "nodebb-plugin-markdown": "7.1.0", "nodebb-plugin-mentions": "1.1.3", - "nodebb-plugin-soundpack-default": "0.1.6", + "nodebb-plugin-soundpack-default": "1.0.0", "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.15", diff --git a/public/src/admin/general/sounds.js b/public/src/admin/general/sounds.js index 64926e60db..259b21f657 100644 --- a/public/src/admin/general/sounds.js +++ b/public/src/admin/general/sounds.js @@ -1,7 +1,7 @@ "use strict"; /* global app, define, socket */ -define('admin/general/sounds', ['sounds', 'settings'], function (Sounds, Settings) { +define('admin/general/sounds', ['sounds', 'settings', 'admin/settings'], function (Sounds, Settings, AdminSettings) { var SoundsAdmin = {}; SoundsAdmin.init = function () { @@ -9,8 +9,8 @@ define('admin/general/sounds', ['sounds', 'settings'], function (Sounds, Setting $('.sounds').find('button[data-action="play"]').on('click', function (e) { e.preventDefault(); - var fileName = $(this).parent().parent().find('select').val(); - Sounds.playFile(fileName); + var soundName = $(this).parent().parent().find('select').val(); + Sounds.playSound(soundName); }); // Load Form Values @@ -26,6 +26,8 @@ define('admin/general/sounds', ['sounds', 'settings'], function (Sounds, Setting app.alertSuccess('[[admin/general/sounds:saved]]'); }); }); + + AdminSettings.prepare(); }; return SoundsAdmin; diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index b2220d69a2..e04d5117ff 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -36,8 +36,8 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds' $('.account').find('button[data-action="play"]').on('click', function (e) { e.preventDefault(); - var fileName = $(this).parent().parent().find('select').val(); - sounds.playFile(fileName); + var soundName = $(this).parent().parent().find('select').val(); + sounds.playSound(soundName); }); toggleCustomRoute(); @@ -89,7 +89,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds' } } - sounds.reloadMapping(); + sounds.loadMap(); if (requireReload && parseInt(app.user.uid, 10) === parseInt(ajaxify.data.theirid, 10)) { app.alert({ diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index f231fd765d..e8a108c46f 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -67,7 +67,7 @@ define('chat', [ if (!isSelf && (!modal.is(':visible') || !app.isFocused)) { app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + username + ']]'); - sounds.play('chat-incoming'); + sounds.play('chat-incoming', 'chat.incoming:' + data.message.mid); taskbar.push('chat', modal.attr('UUID'), { title: username, @@ -89,7 +89,7 @@ define('chat', [ module.toggleNew(modal.attr('UUID'), !isSelf, true); if (!isSelf) { app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + username + ']]'); - sounds.play('chat-incoming'); + sounds.play('chat-incoming', 'chat.incoming:' + data.message.mid); } }); }); diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index c668cbbfc7..f9972ca5a9 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -2,7 +2,7 @@ /* globals define, socket, app, ajaxify, templates, Tinycon*/ -define('notifications', ['sounds', 'translator', 'components'], function (sound, translator, components) { +define('notifications', ['sounds', 'translator', 'components'], function (sounds, translator, components) { var Notifications = {}; var unreadNotifs = {}; @@ -105,7 +105,7 @@ define('notifications', ['sounds', 'translator', 'components'], function (sound, }); if (!unreadNotifs[notifData.nid]) { - sound.play('notification'); + sounds.play('notification', notifData.nid); unreadNotifs[notifData.nid] = true; } }); diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js index a31bbe507a..8fdd5173d0 100644 --- a/public/src/modules/sounds.js +++ b/public/src/modules/sounds.js @@ -1,90 +1,89 @@ "use strict"; /* global app, define, socket, config */ -define('sounds', ['buzz'], function (buzz) { +define('sounds', function () { var Sounds = {}; - var loadedSounds = {}; - var eventSoundMapping; - var files; + var fileMap; + var soundMap; + var cache = {}; - socket.on('event:sounds.reloadMapping', function () { - Sounds.reloadMapping(); - }); - - Sounds.reloadMapping = function () { - socket.emit('modules.sounds.getMapping', function (err, mapping) { + Sounds.loadMap = function loadMap(callback) { + socket.emit('modules.sounds.getUserSoundMap', function (err, map) { if (err) { return app.alertError(err.message); } - eventSoundMapping = mapping; + soundMap = map; + if (callback) { + callback(); + } }); }; function loadData(callback) { - socket.emit('modules.sounds.getData', function (err, data) { - if (err) { - return app.alertError('[sounds] Could not load sound mapping!'); - } - eventSoundMapping = data.mapping; - files = data.files; - callback(); - }); - } - - function isSoundLoaded(fileName) { - return loadedSounds[fileName]; - } - - function loadFile(fileName, callback) { - function createSound() { - if (files && files[fileName]) { - loadedSounds[fileName] = new buzz.sound(files[fileName]); + var outstanding = 2; + function after() { + outstanding -= 1; + if (outstanding === 0 && callback) { + callback(); } - callback(); } - - if (isSoundLoaded(fileName)) { - return callback(); + if (fileMap) { + outstanding -= 1; + } else { + $.getJSON(config.relative_path + '/assets/sounds/fileMap.json', function (map) { + fileMap = map; + after(); + }); } - if (!files || !files[fileName]) { - return loadData(createSound); - } - createSound(); + Sounds.loadMap(after); } - Sounds.play = function (name) { - function play() { - Sounds.playFile(eventSoundMapping[name]); + Sounds.playSound = function playSound(soundName) { + if (!soundMap || !fileMap) { + return loadData(after); } - if (!eventSoundMapping) { - return loadData(play); + function after() { + if (!fileMap[soundName]) { + return; + } + var audio = cache[soundName] = cache[soundName] || new Audio(config.relative_path + '/assets/sounds/' + fileMap[soundName]); + audio.pause(); + audio.currentTime = 0; + audio.play(); } - play(); + after(); }; - Sounds.playFile = function (fileName) { - if (!fileName) { - return; - } - - function play() { - if (loadedSounds[fileName]) { - loadedSounds[fileName].play(); - } else { - app.alertError('[sounds] Not found: ' + fileName); + Sounds.play = function play(type, id) { + function after() { + if (!soundMap[type]) { + return; + } + + if (id) { + if (localStorage.getItem('sounds.handled:' + id)) { + return; + } + localStorage.setItem('sounds.handled:' + id, true); } + + Sounds.playSound(soundMap[type]); } - if (isSoundLoaded(fileName)) { - play(); - } else { - loadFile(fileName, play); + if (!soundMap || !fileMap) { + return loadData(after); } + + after(); }; + socket.on('event:sounds.reloadMapping', function () { + Sounds.loadMap(); + }); + return Sounds; }); diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index 44499e7e68..e4dc33bd89 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -37,11 +37,8 @@ settingsController.get = function (req, res, callback) { homePageRoutes: function (next) { getHomePageRoutes(next); }, - sounds: function (next) { - meta.sounds.getFiles(next); - }, soundsMapping: function (next) { - meta.sounds.getMapping(userData.uid, next); + meta.sounds.getUserSoundMap(userData.uid, next); } }, next); }, @@ -50,19 +47,47 @@ settingsController.get = function (req, res, callback) { userData.languages = results.languages; userData.homePageRoutes = results.homePageRoutes; - var soundSettings = { - 'notificationSound': 'notification', - 'incomingChatSound': 'chat-incoming', - 'outgoingChatSound': 'chat-outgoing' + var types = [ + 'notification', + 'chat-incoming', + 'chat-outgoing', + ]; + var aliases = { + 'notification': 'notificationSound', + 'chat-incoming': 'incomingChatSound', + 'chat-outgoing': 'outgoingChatSound', }; - Object.keys(soundSettings).forEach(function (setting) { - userData[setting] = Object.keys(results.sounds).map(function (name) { - return {name: name, selected: name === results.soundsMapping[soundSettings[setting]]}; + types.forEach(function (type) { + var soundpacks = plugins.soundpacks.map(function (pack) { + var sounds = Object.keys(pack.sounds).map(function (soundName) { + var value = pack.name + ' | ' + soundName; + return { + name: soundName, + value: value, + selected: value === results.soundsMapping[type], + }; + }); + + return { + name: pack.name, + sounds: sounds, + }; }); + + userData[type.replace('-', '_') + '_sound'] = soundpacks; + // fallback + userData[aliases[type]] = soundpacks.concat.apply([], soundpacks.map(function (pack) { + return pack.sounds.map(function (sound) { + return { + name: sound.value, + selected: sound.selected, + }; + }); + })); }); - plugins.fireHook('filter:user.customSettings', {settings: results.settings, customSettings: [], uid: req.uid}, next); + plugins.fireHook('filter:user.customSettings', { settings: results.settings, customSettings: [], uid: req.uid }, next); }, function (data, next) { userData.customSettings = data.customSettings; @@ -75,10 +100,10 @@ settingsController.get = function (req, res, callback) { } userData.dailyDigestFreqOptions = [ - {value: 'off', name: '[[user:digest_off]]', selected: 'off' === userData.settings.dailyDigestFreq}, - {value: 'day', name: '[[user:digest_daily]]', selected: 'day' === userData.settings.dailyDigestFreq}, - {value: 'week', name: '[[user:digest_weekly]]', selected: 'week' === userData.settings.dailyDigestFreq}, - {value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq} + { value: 'off', name: '[[user:digest_off]]', selected: 'off' === userData.settings.dailyDigestFreq }, + { value: 'day', name: '[[user:digest_daily]]', selected: 'day' === userData.settings.dailyDigestFreq }, + { value: 'week', name: '[[user:digest_weekly]]', selected: 'week' === userData.settings.dailyDigestFreq }, + { value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq } ]; diff --git a/src/controllers/admin/sounds.js b/src/controllers/admin/sounds.js index 801a2067ac..185b09ec4c 100644 --- a/src/controllers/admin/sounds.js +++ b/src/controllers/admin/sounds.js @@ -1,24 +1,46 @@ 'use strict'; -var meta = require('../../meta'); +var plugins = require('../../plugins'); +var db = require('../../database'); var soundsController = {}; soundsController.get = function (req, res, next) { - meta.sounds.getFiles(function (err, sounds) { + db.getObject('settings:sounds', function (err, settings) { if (err) { return next(err); } + + settings = settings || {}; - sounds = Object.keys(sounds).map(function (name) { - return { - name: name - }; - }); + var types = [ + 'notification', + 'chat-incoming', + 'chat-outgoing', + ]; + var output = {}; + + types.forEach(function (type) { + var soundpacks = plugins.soundpacks.map(function (pack) { + var sounds = Object.keys(pack.sounds).map(function (soundName) { + var value = pack.name + ' | ' + soundName; + return { + name: soundName, + value: value, + selected: value === settings[type], + }; + }); - res.render('admin/general/sounds', { - sounds: sounds + return { + name: pack.name, + sounds: sounds, + }; + }); + + output[type.replace('-', '_') + '_sound'] = soundpacks; }); + + res.render('admin/general/sounds', output); }); }; diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index 346e7765e4..0b7104f1de 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -5,6 +5,8 @@ var path = require('path'); var async = require('async'); var nconf = require('nconf'); var winston = require('winston'); + +var meta = require('../../meta'); var file = require('../../file'); var image = require('../../image'); var plugins = require('../../plugins'); @@ -105,12 +107,7 @@ uploadsController.uploadSound = function (req, res, next) { return next(err); } - var soundsPath = path.join(__dirname, '../../../build/public/sounds'), - filePath = path.join(nconf.get('upload_path'), 'sounds', uploadedFile.name); - - file.link(filePath, path.join(soundsPath, path.basename(filePath))); - - fs.unlink(uploadedFile.path, function (err) { + meta.sounds.build(function (err) { if (err) { return next(err); } diff --git a/src/meta/build.js b/src/meta/build.js index 3f2feb8a2a..104631ffbb 100644 --- a/src/meta/build.js +++ b/src/meta/build.js @@ -5,7 +5,7 @@ var winston = require('winston'); var buildStart; -var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang']; +var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang', 'sound']; exports.buildAll = function (callback) { exports.build(valid.join(','), callback); @@ -46,7 +46,7 @@ exports.buildTargets = function (targets, callback) { var cacheBuster = require('./cacheBuster'); var meta = require('../meta'); var numCpus = require('os').cpus().length; - var strategy = (targets.length > 1 && numCpus > 1); + var parallel = targets.length > 1 && numCpus > 1; buildStart = buildStart || Date.now(); @@ -59,13 +59,13 @@ exports.buildTargets = function (targets, callback) { next(); }; - if (strategy) { + if (parallel) { winston.verbose('[build] Utilising multiple cores/processes'); } else { winston.verbose('[build] Utilising single-core'); } - async[strategy ? 'parallel' : 'series']([ + async[parallel ? 'parallel' : 'series']([ function (next) { if (targets.indexOf('js') !== -1) { winston.info('[build] Building javascript'); @@ -111,6 +111,12 @@ exports.buildTargets = function (targets, callback) { meta.languages.build(step.bind(this, startTime, target, next)); break; + case 'sound': + winston.info('[build] Linking sound files'); + startTime = Date.now(); + meta.sounds.build(step.bind(this, startTime, target, next)); + break; + default: winston.warn('[build] Unknown build target: \'' + target + '\''); setImmediate(next); diff --git a/src/meta/sounds.js b/src/meta/sounds.js index d237a51273..00bcbc2410 100644 --- a/src/meta/sounds.js +++ b/src/meta/sounds.js @@ -2,71 +2,101 @@ var path = require('path'); var fs = require('fs'); -var nconf = require('nconf'); -var winston = require('winston'); var rimraf = require('rimraf'); var mkdirp = require('mkdirp'); var async = require('async'); var file = require('../file'); var plugins = require('../plugins'); +var user = require('../user'); var db = require('../database'); -module.exports = function (Meta) { +var soundsPath = path.join(__dirname, '../../build/public/sounds'); +var uploadsPath = path.join(__dirname, '../../public/uploads/sounds'); +module.exports = function (Meta) { Meta.sounds = {}; - Meta.sounds.init = function (callback) { - if (nconf.get('isPrimary') === 'true') { - setupSounds(callback); - } else { - if (typeof callback === 'function') { - callback(); + Meta.sounds.addUploads = function addUploads(callback) { + fs.readdir(uploadsPath, function (err, files) { + if (err) { + if (err.code !== 'ENOENT') { + return callback(err); + } + + files = []; } - } - }; - Meta.sounds.getFiles = function (callback) { - async.waterfall([ - function (next) { - fs.readdir(path.join(__dirname, '../../build/public/sounds'), next); - }, - function (sounds, next) { - fs.readdir(path.join(nconf.get('upload_path'), 'sounds'), function (err, uploaded) { - if (err) { - if (err.code === 'ENOENT') { - return next(null, sounds); - } - return next(err); - } - next(null, sounds.concat(uploaded)); + var uploadSounds = files.reduce(function (prev, fileName) { + var name = fileName.split('.'); + if (!name.length || !name[0].length) { + return prev; + } + name = name[0]; + name = name[0].toUpperCase() + name.slice(1); + + prev[name] = fileName; + return prev; + }, {}); + + plugins.soundpacks = plugins.soundpacks.filter(function (pack) { + return pack.name !== 'Uploads'; + }); + if (Object.keys(uploadSounds).length) { + plugins.soundpacks.push({ + name: 'Uploads', + id: 'uploads', + dir: uploadsPath, + sounds: uploadSounds, }); } - ], function (err, files) { - if (err) { - winston.error('Could not get local sound files:' + err.message); - console.log(err.stack); - return callback(null, []); - } - var localList = {}; + callback(); + }); + }; - // Filter out hidden files - files = files.filter(function (filename) { - return !filename.startsWith('.'); - }); + Meta.sounds.build = function build(callback) { + Meta.sounds.addUploads(function (err) { + if (err) { + return callback(err); + } - // Return proper paths - files.forEach(function (filename) { - localList[filename] = nconf.get('relative_path') + '/assets/sounds/' + filename; + var map = plugins.soundpacks.map(function (pack) { + return Object.keys(pack.sounds).reduce(function (prev, soundName) { + var soundPath = pack.sounds[soundName]; + prev[pack.name + ' | ' + soundName] = pack.id + '/' + soundPath; + return prev; + }, {}); }); - - callback(null, localList); + map.unshift({}); + map = Object.assign.apply(null, map); + + async.series([ + function (next) { + rimraf(soundsPath, next); + }, + function (next) { + mkdirp(soundsPath, next); + }, + function (cb) { + async.parallel([ + function (next) { + fs.writeFile(path.join(soundsPath, 'fileMap.json'), JSON.stringify(map), next); + }, + function (next) { + async.each(plugins.soundpacks, function (pack, next) { + file.linkDirs(pack.dir, path.join(soundsPath, pack.id), next); + }, next); + }, + ], cb); + }, + ], callback); }); }; - Meta.sounds.getMapping = function (uid, callback) { - var user = require('../user'); + var keys = ['chat-incoming', 'chat-outgoing', 'notification']; + + Meta.sounds.getUserSoundMap = function getUserSoundMap(uid, callback) { async.parallel({ defaultMapping: function (next) { db.getObject('settings:sounds', next); @@ -78,82 +108,25 @@ module.exports = function (Meta) { if (err) { return callback(err); } + var userSettings = results.userSettings; + userSettings = { + notification: userSettings.notificationSound, + 'chat-incoming': userSettings.incomingChatSound, + 'chat-outgoing': userSettings.outgoingChatSound, + }; var defaultMapping = results.defaultMapping || {}; var soundMapping = {}; - soundMapping.notification = (userSettings.notificationSound || userSettings.notificationSound === '') ? - userSettings.notificationSound : defaultMapping.notification || ''; - - soundMapping['chat-incoming'] = (userSettings.incomingChatSound || userSettings.incomingChatSound === '') ? - userSettings.incomingChatSound : defaultMapping['chat-incoming'] || ''; - soundMapping['chat-outgoing'] = (userSettings.outgoingChatSound || userSettings.outgoingChatSound === '') ? - userSettings.outgoingChatSound : defaultMapping['chat-outgoing'] || ''; + keys.forEach(function (key) { + if (userSettings[key] || userSettings[key] === '') { + soundMapping[key] = userSettings[key] || null; + } else { + soundMapping[key] = defaultMapping[key] || null; + } + }); callback(null, soundMapping); }); }; - - function setupSounds(callback) { - var soundsPath = path.join(__dirname, '../../build/public/sounds'); - - async.waterfall([ - function (next) { - fs.readdir(path.join(nconf.get('upload_path'), 'sounds'), function (err, files) { - if (err) { - if (err.code === 'ENOENT') { - return next(null, []); - } - return next(err); - } - - next(null, files); - }); - }, - function (uploaded, next) { - uploaded = uploaded.filter(function (filename) { - return !filename.startsWith('.'); - }).map(function (filename) { - return path.join(nconf.get('upload_path'), 'sounds', filename); - }); - - plugins.fireHook('filter:sounds.get', uploaded, function (err, filePaths) { - if (err) { - winston.error('Could not initialise sound files:' + err.message); - return; - } - - // Clear the sounds directory - async.series([ - function (next) { - rimraf(soundsPath, next); - }, - function (next) { - mkdirp(soundsPath, next); - } - ], function (err) { - if (err) { - winston.error('Could not initialise sound files:' + err.message); - return; - } - - // Link paths - async.each(filePaths, function (filePath, next) { - file.link(filePath, path.join(soundsPath, path.basename(filePath)), next); - }, function (err) { - if (!err) { - winston.verbose('[sounds] Sounds OK'); - } else { - winston.error('[sounds] Could not initialise sounds: ' + err.message); - } - - if (typeof next === 'function') { - next(); - } - }); - }); - }); - } - ], callback); - } -}; \ No newline at end of file +}; diff --git a/src/plugins.js b/src/plugins.js index 789c92b72c..15f4b91650 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -32,6 +32,7 @@ var middleware; Plugins.libraryPaths = []; Plugins.versionWarning = []; Plugins.languageCodes = []; + Plugins.soundpacks = []; Plugins.initialized = false; diff --git a/src/plugins/load.js b/src/plugins/load.js index 01dedc4e45..c7dd924e37 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -37,10 +37,11 @@ module.exports = function (Plugins) { }; Plugins.prepareForBuild = function (callback) { - Plugins.cssFiles.length = 0; - Plugins.lessFiles.length = 0; - Plugins.clientScripts.length = 0; - Plugins.acpScripts.length = 0; + Plugins.cssFiles = []; + Plugins.lessFiles = []; + Plugins.clientScripts = []; + Plugins.acpScripts = []; + Plugins.soundpacks = []; async.waterfall([ async.apply(Plugins.getPluginPaths), @@ -57,6 +58,7 @@ module.exports = function (Plugins) { async.apply(mapClientSideScripts, pluginData), async.apply(mapClientModules, pluginData), async.apply(mapStaticDirectories, pluginData, pluginData.path), + async.apply(mapSoundpack, pluginData), ], next); }, next); } @@ -93,6 +95,9 @@ module.exports = function (Plugins) { function (next) { mapClientModules(pluginData, next); }, + function (next) { + mapSoundpack(pluginData, next); + }, ], function (err) { if (err) { winston.verbose('[plugins] Could not load plugin : ' + pluginData.id); @@ -251,6 +256,35 @@ module.exports = function (Plugins) { callback(); } + function mapSoundpack(pluginData, callback) { + var soundpack = pluginData.soundpack; + if (!soundpack || !soundpack.dir || !soundpack.sounds) { + return callback(); + } + soundpack.name = soundpack.name || pluginData.name; + soundpack.id = pluginData.id; + soundpack.dir = path.join(pluginData.path, soundpack.dir); + async.each(Object.keys(soundpack.sounds), function (key, next) { + file.exists(path.join(soundpack.dir, soundpack.sounds[key]), function (exists) { + if (!exists) { + delete soundpack.sounds[key]; + } + + next(); + }); + }, function (err) { + if (err) { + return callback(err); + } + + if (Object.keys(soundpack.sounds).length) { + Plugins.soundpacks.push(soundpack); + } + + callback(); + }); + } + function resolveModulePath(fullPath, relPath) { /** * With npm@3, dependencies can become flattened, and appear at the root level. diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index a23e9c64bb..473a32e5d9 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -341,20 +341,8 @@ SocketModules.chats.getMessages = function (socket, data, callback) { }; /* Sounds */ -SocketModules.sounds.getSounds = function (socket, data, callback) { - // Read sounds from local directory - meta.sounds.getFiles(callback); -}; - -SocketModules.sounds.getMapping = function (socket, data, callback) { - meta.sounds.getMapping(socket.uid, callback); -}; - -SocketModules.sounds.getData = function (socket, data, callback) { - async.parallel({ - mapping: async.apply(meta.sounds.getMapping, socket.uid), - files: async.apply(meta.sounds.getFiles) - }, callback); +SocketModules.sounds.getUserSoundMap = function getUserSoundMap(socket, data, callback) { + meta.sounds.getUserSoundMap(socket.uid, callback); }; module.exports = SocketModules; diff --git a/src/views/admin/general/sounds.tpl b/src/views/admin/general/sounds.tpl index d37447d56e..fc8f572b37 100644 --- a/src/views/admin/general/sounds.tpl +++ b/src/views/admin/general/sounds.tpl @@ -9,9 +9,15 @@
@@ -29,9 +35,15 @@
@@ -44,9 +56,15 @@
@@ -56,7 +74,15 @@
- +
diff --git a/src/webserver.js b/src/webserver.js index ee2605cdb2..f6c89b5451 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -103,9 +103,9 @@ function initializeNodeBB(callback) { }, function (next) { async.series([ - async.apply(meta.sounds.init), - async.apply(languages.init), - async.apply(meta.blacklist.load) + meta.sounds.addUploads, + languages.init, + meta.blacklist.load, ], next); } ], callback); From 9f15008247469a42d575453970e0f52020f4bc7a Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Fri, 17 Feb 2017 11:19:30 -0700 Subject: [PATCH 02/22] Address review issues --- public/src/modules/sounds.js | 9 ++++-- src/controllers/accounts/settings.js | 2 +- src/controllers/admin/sounds.js | 2 +- src/plugins/load.js | 10 +++---- src/views/admin/general/sounds.tpl | 42 ++++++++++++++-------------- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js index 8fdd5173d0..26d64079ca 100644 --- a/public/src/modules/sounds.js +++ b/public/src/modules/sounds.js @@ -65,10 +65,15 @@ define('sounds', function () { } if (id) { - if (localStorage.getItem('sounds.handled:' + id)) { + var item = 'sounds.handled:' + id; + if (sessionStorage.getItem(item)) { return; } - localStorage.setItem('sounds.handled:' + id, true); + sessionStorage.setItem(item, true); + + setTimeout(function () { + sessionStorage.removeItem(item); + }, 5000); } Sounds.playSound(soundMap[type]); diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index e4dc33bd89..68cef8be5f 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -75,7 +75,7 @@ settingsController.get = function (req, res, callback) { }; }); - userData[type.replace('-', '_') + '_sound'] = soundpacks; + userData[type + '-sound'] = soundpacks; // fallback userData[aliases[type]] = soundpacks.concat.apply([], soundpacks.map(function (pack) { return pack.sounds.map(function (sound) { diff --git a/src/controllers/admin/sounds.js b/src/controllers/admin/sounds.js index 185b09ec4c..b042048313 100644 --- a/src/controllers/admin/sounds.js +++ b/src/controllers/admin/sounds.js @@ -37,7 +37,7 @@ soundsController.get = function (req, res, next) { }; }); - output[type.replace('-', '_') + '_sound'] = soundpacks; + output[type + '-sound'] = soundpacks; }); res.render('admin/general/sounds', output); diff --git a/src/plugins/load.js b/src/plugins/load.js index c7dd924e37..c6a9429667 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -37,11 +37,11 @@ module.exports = function (Plugins) { }; Plugins.prepareForBuild = function (callback) { - Plugins.cssFiles = []; - Plugins.lessFiles = []; - Plugins.clientScripts = []; - Plugins.acpScripts = []; - Plugins.soundpacks = []; + Plugins.cssFiles.length = 0; + Plugins.lessFiles.length = 0; + Plugins.clientScripts.length = 0; + Plugins.acpScripts.length = 0; + Plugins.soundpacks.length = 0; async.waterfall([ async.apply(Plugins.getPluginPaths), diff --git a/src/views/admin/general/sounds.tpl b/src/views/admin/general/sounds.tpl index fc8f572b37..79b99229f5 100644 --- a/src/views/admin/general/sounds.tpl +++ b/src/views/admin/general/sounds.tpl @@ -9,15 +9,15 @@
@@ -35,15 +35,15 @@
@@ -56,15 +56,15 @@
From a99572fc485f9e45c671ca09f16ae308a849c310 Mon Sep 17 00:00:00 2001 From: pichalite Date: Sun, 19 Feb 2017 08:30:30 +0000 Subject: [PATCH 03/22] Use ACP profile image dimension setting in cropper --- public/src/client/account/edit.js | 22 +++-- public/src/client/account/header.js | 10 ++- public/src/client/groups/details.js | 118 ++++++++++++++------------- public/src/modules/pictureCropper.js | 26 ++++++ src/controllers/accounts/edit.js | 33 ++++++-- src/views/modals/crop_picture.tpl | 69 ++++++++-------- 6 files changed, 168 insertions(+), 110 deletions(-) diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js index bc80fd49e5..86b9dcafe6 100644 --- a/public/src/client/account/edit.js +++ b/public/src/client/account/edit.js @@ -73,7 +73,9 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components' function handleImageChange() { $('#changePictureBtn').on('click', function () { - socket.emit('user.getProfilePictures', {uid: ajaxify.data.uid}, function (err, pictures) { + socket.emit('user.getProfilePictures', { + uid: ajaxify.data.uid + }, function (err, pictures) { if (err) { return app.alertError(err.message); } @@ -216,10 +218,13 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components' pictureCropper.show({ socketMethod: 'user.uploadCroppedPicture', - aspectRatio: '1 / 1', + aspectRatio: 1 / 1, paramName: 'uid', paramValue: ajaxify.data.theirid, fileSize: ajaxify.data.maximumProfileImageSize, + allowSkippingCrop: false, + restrictImageDimension: true, + imageDimension: ajaxify.data.profileImageDimension, title: '[[user:upload_picture]]', description: '[[user:upload_a_picture]]', accept: '.png,.jpg,.bmp' @@ -242,17 +247,20 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components' if (!url) { return; } - + uploadModal.modal('hide'); - + pictureCropper.handleImageCrop({ url: url, socketMethod: 'user.uploadCroppedPicture', aspectRatio: '1 / 1', + allowSkippingCrop: false, + restrictImageDimension: true, + imageDimension: ajaxify.data.profileImageDimension, paramName: 'uid', paramValue: ajaxify.data.theirid, }, onUploadComplete); - + return false; }); }); @@ -262,7 +270,9 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components' }); modal.find('[data-action="remove-uploaded"]').on('click', function () { - socket.emit('user.removeUploadedPicture', {uid: ajaxify.data.theirid}, function (err) { + socket.emit('user.removeUploadedPicture', { + uid: ajaxify.data.theirid + }, function (err) { modal.modal('hide'); if (err) { return app.alertError(err.message); diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index 9e1a29577b..63869af05b 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -83,7 +83,9 @@ define('forum/account/header', [ pictureCropper.show({ title: '[[user:upload_cover_picture]]', socketMethod: 'user.updateCover', - aspectRatio: '16 / 9', + aspectRatio: NaN, + allowSkippingCrop: true, + restrictImageDimension: false, paramName: 'uid', paramValue: ajaxify.data.theirid, accept: '.png,.jpg,.bmp' @@ -131,7 +133,11 @@ define('forum/account/header', [ }, {}); var until = parseInt(formData.length, 10) ? (Date.now() + formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1)) : 0; - socket.emit('user.banUsers', { uids: [ajaxify.data.theirid], until: until, reason: formData.reason || '' }, function (err) { + socket.emit('user.banUsers', { + uids: [ajaxify.data.theirid], + until: until, + reason: formData.reason || '' + }, function (err) { if (err) { return app.alertError(err.message); } diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 771c355fcc..6d0559add3 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -34,7 +34,9 @@ define('forum/groups/details', [ pictureCropper.show({ title: '[[groups:upload-group-cover]]', socketMethod: 'groups.cover.update', - aspectRatio: '16 / 9', + aspectRatio: NaN, + allowSkippingCrop: true, + restrictImageDimension: false, paramName: 'groupName', paramValue: groupName }, function (imageUrlOnServer) { @@ -59,62 +61,62 @@ define('forum/groups/details', [ uid = userRow.attr('data-uid'), action = btnEl.attr('data-action'); - switch(action) { - case 'toggleOwnership': - socket.emit('groups.' + (isOwner ? 'rescind' : 'grant'), { - toUid: uid, - groupName: groupName - }, function (err) { - if (!err) { - ownerFlagEl.toggleClass('invisible'); - } else { - app.alertError(err.message); - } - }); - break; - - case 'kick': - socket.emit('groups.kick', { - uid: uid, - groupName: groupName - }, function (err) { - if (!err) { - userRow.slideUp().remove(); - } else { - app.alertError(err.message); - } - }); - break; - - case 'update': - Details.update(); - break; - - case 'delete': - Details.deleteGroup(); - break; - - case 'join': // intentional fall-throughs! - case 'leave': - case 'accept': - case 'reject': - case 'issueInvite': - case 'rescindInvite': - case 'acceptInvite': - case 'rejectInvite': - case 'acceptAll': - case 'rejectAll': - socket.emit('groups.' + action, { - toUid: uid, - groupName: groupName - }, function (err) { - if (!err) { - ajaxify.refresh(); - } else { - app.alertError(err.message); - } - }); - break; + switch (action) { + case 'toggleOwnership': + socket.emit('groups.' + (isOwner ? 'rescind' : 'grant'), { + toUid: uid, + groupName: groupName + }, function (err) { + if (!err) { + ownerFlagEl.toggleClass('invisible'); + } else { + app.alertError(err.message); + } + }); + break; + + case 'kick': + socket.emit('groups.kick', { + uid: uid, + groupName: groupName + }, function (err) { + if (!err) { + userRow.slideUp().remove(); + } else { + app.alertError(err.message); + } + }); + break; + + case 'update': + Details.update(); + break; + + case 'delete': + Details.deleteGroup(); + break; + + case 'join': // intentional fall-throughs! + case 'leave': + case 'accept': + case 'reject': + case 'issueInvite': + case 'rescindInvite': + case 'acceptInvite': + case 'rejectInvite': + case 'acceptAll': + case 'rejectAll': + socket.emit('groups.' + action, { + toUid: uid, + groupName: groupName + }, function (err) { + if (!err) { + ajaxify.refresh(); + } else { + app.alertError(err.message); + } + }); + break; } }); }; @@ -273,7 +275,7 @@ define('forum/groups/details', [ if (!confirm) { return; } - + socket.emit('groups.cover.remove', { groupName: ajaxify.data.group.name }, function (err) { diff --git a/public/src/modules/pictureCropper.js b/public/src/modules/pictureCropper.js index b04bc48e97..d640bd94bb 100644 --- a/public/src/modules/pictureCropper.js +++ b/public/src/modules/pictureCropper.js @@ -44,7 +44,30 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe var cropperTool = new cropper.default(img, { aspectRatio: data.aspectRatio, viewMode: 1, + cropmove: function (e) { + if (data.restrictImageDimension) { + if (cropperTool.cropBoxData.width > data.imageDimension) { + cropperTool.setCropBoxData({ + width: data.imageDimension + }); + } + if (cropperTool.cropBoxData.height > data.imageDimension) { + cropperTool.setCropBoxData({ + height: data.imageDimension + }); + } + } + }, ready: function () { + if (data.restrictImageDimension) { + var origDimension = (img.width < img.height) ? img.width : img.height; + var dimension = (origDimension > data.imageDimension) ? data.imageDimension : origDimension; + cropperTool.setCropBoxData({ + width: dimension, + height: dimension + }); + } + cropperModal.find('.rotate').on('click', function () { var degrees = this.getAttribute("data-degrees"); cropperTool.rotate(degrees); @@ -132,6 +155,9 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe imageType: imageType, socketMethod: data.socketMethod, aspectRatio: data.aspectRatio, + allowSkippingCrop: data.allowSkippingCrop, + restrictImageDimension: data.restrictImageDimension, + imageDimension: data.imageDimension, paramName: data.paramName, paramValue: data.paramValue }, callback); diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js index c9549a1def..58409060e4 100644 --- a/src/controllers/accounts/edit.js +++ b/src/controllers/accounts/edit.js @@ -27,6 +27,7 @@ editController.get = function (req, res, callback) { userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10); userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads) === 1; userData.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1; + userData.profileImageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; userData.groups = userData.groups.filter(function (group) { return group && group.userTitleEnabled && !groups.isPrivilegeGroup(group.name) && group.name !== 'registered-users'; @@ -36,7 +37,12 @@ editController.get = function (req, res, callback) { }); userData.title = '[[pages:account/edit, ' + userData.username + ']]'; - userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]); + userData.breadcrumbs = helpers.buildBreadcrumbs([{ + text: userData.username, + url: '/user/' + userData.userslug + }, { + text: '[[user:edit]]' + }]); userData.editButtons = []; plugins.fireHook('filter:user.account.edit', userData, function (err, userData) { @@ -75,11 +81,15 @@ function renderRoute(name, req, res, next) { } userData.title = '[[pages:account/edit/' + name + ', ' + userData.username + ']]'; - userData.breadcrumbs = helpers.buildBreadcrumbs([ - {text: userData.username, url: '/user/' + userData.userslug}, - {text: '[[user:edit]]', url: '/user/' + userData.userslug + '/edit'}, - {text: '[[user:' + name + ']]'} - ]); + userData.breadcrumbs = helpers.buildBreadcrumbs([{ + text: userData.username, + url: '/user/' + userData.userslug + }, { + text: '[[user:edit]]', + url: '/user/' + userData.userslug + '/edit' + }, { + text: '[[user:' + name + ']]' + }]); res.render('account/edit/' + name, userData); }); @@ -139,7 +149,10 @@ editController.uploadPicture = function (req, res, next) { return next(err); } - res.json([{name: userPhoto.name, url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]); + res.json([{ + name: userPhoto.name, + url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url + }]); }); }; @@ -154,8 +167,10 @@ editController.uploadCoverPicture = function (req, res, next) { return next(err); } - res.json([{ url: image.url }]); + res.json([{ + url: image.url + }]); }); }; -module.exports = editController; \ No newline at end of file +module.exports = editController; diff --git a/src/views/modals/crop_picture.tpl b/src/views/modals/crop_picture.tpl index ba98860df4..f54370a0bb 100644 --- a/src/views/modals/crop_picture.tpl +++ b/src/views/modals/crop_picture.tpl @@ -1,40 +1,39 @@ -