mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	Unwrap meta modules
This commit is contained in:
		
							
								
								
									
										20
									
								
								src/meta.js
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								src/meta.js
									
									
									
									
									
								
							| @@ -12,16 +12,16 @@ var Meta = module.exports; | ||||
|  | ||||
| Meta.reloadRequired = false; | ||||
|  | ||||
| require('./meta/configs')(Meta); | ||||
| require('./meta/themes')(Meta); | ||||
| require('./meta/js')(Meta); | ||||
| require('./meta/css')(Meta); | ||||
| require('./meta/sounds')(Meta); | ||||
| require('./meta/settings')(Meta); | ||||
| require('./meta/logs')(Meta); | ||||
| require('./meta/errors')(Meta); | ||||
| require('./meta/tags')(Meta); | ||||
| require('./meta/dependencies')(Meta); | ||||
| Meta.configs = require('./meta/configs'); | ||||
| Meta.themes = require('./meta/themes'); | ||||
| Meta.js = require('./meta/js'); | ||||
| Meta.css = require('./meta/css'); | ||||
| Meta.sounds = require('./meta/sounds'); | ||||
| Meta.settings = require('./meta/settings'); | ||||
| Meta.logs = require('./meta/logs'); | ||||
| Meta.errors = require('./meta/errors'); | ||||
| Meta.tags = require('./meta/tags'); | ||||
| Meta.dependencies = require('./meta/dependencies'); | ||||
| Meta.templates = require('./meta/templates'); | ||||
| Meta.blacklist = require('./meta/blacklist'); | ||||
| Meta.languages = require('./meta/languages'); | ||||
|   | ||||
| @@ -6,142 +6,142 @@ var nconf = require('nconf'); | ||||
|  | ||||
| var db = require('../database'); | ||||
| var pubsub = require('../pubsub'); | ||||
| var Meta = require('../meta'); | ||||
| var cacheBuster = require('./cacheBuster'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.config = {}; | ||||
| 	Meta.configs = {}; | ||||
| var Configs = module.exports; | ||||
|  | ||||
| 	Meta.configs.init = function (callback) { | ||||
| 		delete Meta.config; | ||||
| Meta.config = {}; | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				Meta.configs.list(next); | ||||
| 			}, | ||||
| 			function (config, next) { | ||||
| 				cacheBuster.read(function (err, buster) { | ||||
| 					if (err) { | ||||
| 						return next(err); | ||||
| 					} | ||||
| Configs.init = function (callback) { | ||||
| 	Meta.config = null; | ||||
|  | ||||
| 					config['cache-buster'] = 'v=' + (buster || Date.now()); | ||||
|  | ||||
| 					Meta.config = config; | ||||
| 					next(); | ||||
| 				}); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.configs.list = function (callback) { | ||||
| 		db.getObject('config', function (err, config) { | ||||
| 			config = config || {}; | ||||
| 			config.version = nconf.get('version'); | ||||
| 			config.registry = nconf.get('registry'); | ||||
| 			callback(err, config); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.configs.get = function (field, callback) { | ||||
| 		db.getObjectField('config', field, callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.configs.getFields = function (fields, callback) { | ||||
| 		db.getObjectFields('config', fields, callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.configs.set = function (field, value, callback) { | ||||
| 		callback = callback || function () {}; | ||||
| 		if (!field) { | ||||
| 			return callback(new Error('[[error:invalid-data]]')); | ||||
| 		} | ||||
|  | ||||
| 		var data = {}; | ||||
| 		data[field] = value; | ||||
| 		Meta.configs.setMultiple(data, callback); | ||||
| 	}; | ||||
|  | ||||
|  | ||||
| 	Meta.configs.setMultiple = function (data, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				processConfig(data, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				db.setObject('config', data, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				updateConfig(data); | ||||
| 				setImmediate(next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	function processConfig(data, callback) { | ||||
| 		if (data.customCSS) { | ||||
| 			return saveRenderedCss(data, callback); | ||||
| 		} | ||||
| 		setImmediate(callback); | ||||
| 	} | ||||
|  | ||||
| 	function saveRenderedCss(data, callback) { | ||||
| 		var less = require('less'); | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				less.render(data.customCSS, { | ||||
| 					compress: true, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (lessObject, next) { | ||||
| 				data.renderedCustomCSS = lessObject.css; | ||||
| 				setImmediate(next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	} | ||||
|  | ||||
| 	function updateConfig(config) { | ||||
| 		updateLocalConfig(config); | ||||
| 		pubsub.publish('config:update', config); | ||||
| 	} | ||||
|  | ||||
| 	function updateLocalConfig(config) { | ||||
| 		for (var field in config) { | ||||
| 			if (config.hasOwnProperty(field)) { | ||||
| 				Meta.config[field] = config[field]; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pubsub.on('config:update', function onConfigReceived(config) { | ||||
| 		if (typeof config === 'object' && Meta.config) { | ||||
| 			updateLocalConfig(config); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	Meta.configs.setOnEmpty = function (values, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.getObject('config', next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				data = data || {}; | ||||
| 				var empty = {}; | ||||
| 				Object.keys(values).forEach(function (key) { | ||||
| 					if (!data.hasOwnProperty(key)) { | ||||
| 						empty[key] = values[key]; | ||||
| 					} | ||||
| 				}); | ||||
| 				if (Object.keys(empty).length) { | ||||
| 					db.setObject('config', empty, next); | ||||
| 				} else { | ||||
| 					setImmediate(next); | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			Configs.list(next); | ||||
| 		}, | ||||
| 		function (config, next) { | ||||
| 			cacheBuster.read(function (err, buster) { | ||||
| 				if (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.configs.remove = function (field, callback) { | ||||
| 		db.deleteObjectField('config', field, callback); | ||||
| 	}; | ||||
| 				config['cache-buster'] = 'v=' + (buster || Date.now()); | ||||
|  | ||||
| 				Meta.config = config; | ||||
| 				next(); | ||||
| 			}); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| Configs.list = function (callback) { | ||||
| 	db.getObject('config', function (err, config) { | ||||
| 		config = config || {}; | ||||
| 		config.version = nconf.get('version'); | ||||
| 		config.registry = nconf.get('registry'); | ||||
| 		callback(err, config); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| Configs.get = function (field, callback) { | ||||
| 	db.getObjectField('config', field, callback); | ||||
| }; | ||||
|  | ||||
| Configs.getFields = function (fields, callback) { | ||||
| 	db.getObjectFields('config', fields, callback); | ||||
| }; | ||||
|  | ||||
| Configs.set = function (field, value, callback) { | ||||
| 	callback = callback || function () {}; | ||||
| 	if (!field) { | ||||
| 		return callback(new Error('[[error:invalid-data]]')); | ||||
| 	} | ||||
|  | ||||
| 	var data = {}; | ||||
| 	data[field] = value; | ||||
| 	Configs.setMultiple(data, callback); | ||||
| }; | ||||
|  | ||||
|  | ||||
| Configs.setMultiple = function (data, callback) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			processConfig(data, next); | ||||
| 		}, | ||||
| 		function (next) { | ||||
| 			db.setObject('config', data, next); | ||||
| 		}, | ||||
| 		function (next) { | ||||
| 			updateConfig(data); | ||||
| 			setImmediate(next); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| function processConfig(data, callback) { | ||||
| 	if (data.customCSS) { | ||||
| 		return saveRenderedCss(data, callback); | ||||
| 	} | ||||
| 	setImmediate(callback); | ||||
| } | ||||
|  | ||||
| function saveRenderedCss(data, callback) { | ||||
| 	var less = require('less'); | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			less.render(data.customCSS, { | ||||
| 				compress: true, | ||||
| 			}, next); | ||||
| 		}, | ||||
| 		function (lessObject, next) { | ||||
| 			data.renderedCustomCSS = lessObject.css; | ||||
| 			setImmediate(next); | ||||
| 		}, | ||||
| 	], callback); | ||||
| } | ||||
|  | ||||
| function updateConfig(config) { | ||||
| 	updateLocalConfig(config); | ||||
| 	pubsub.publish('config:update', config); | ||||
| } | ||||
|  | ||||
| function updateLocalConfig(config) { | ||||
| 	for (var field in config) { | ||||
| 		if (config.hasOwnProperty(field)) { | ||||
| 			Meta.config[field] = config[field]; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| pubsub.on('config:update', function onConfigReceived(config) { | ||||
| 	if (typeof config === 'object' && Meta.config) { | ||||
| 		updateLocalConfig(config); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| Configs.setOnEmpty = function (values, callback) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			db.getObject('config', next); | ||||
| 		}, | ||||
| 		function (data, next) { | ||||
| 			data = data || {}; | ||||
| 			var empty = {}; | ||||
| 			Object.keys(values).forEach(function (key) { | ||||
| 				if (!data.hasOwnProperty(key)) { | ||||
| 					empty[key] = values[key]; | ||||
| 				} | ||||
| 			}); | ||||
| 			if (Object.keys(empty).length) { | ||||
| 				db.setObject('config', empty, next); | ||||
| 			} else { | ||||
| 				setImmediate(next); | ||||
| 			} | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| Configs.remove = function (field, callback) { | ||||
| 	db.deleteObjectField('config', field, callback); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										304
									
								
								src/meta/css.js
									
									
									
									
									
								
							
							
						
						
									
										304
									
								
								src/meta/css.js
									
									
									
									
									
								
							| @@ -11,158 +11,156 @@ var db = require('../database'); | ||||
| var file = require('../file'); | ||||
| var minifier = require('./minifier'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.css = {}; | ||||
| var CSS = module.exports; | ||||
|  | ||||
| 	var buildImports = { | ||||
| 		client: function (source) { | ||||
| 			return '@import "./theme";\n' + source + '\n' + [ | ||||
| 				'@import "font-awesome";', | ||||
| 				'@import (inline) "../public/vendor/jquery/css/smoothness/jquery-ui.css";', | ||||
| 				'@import (inline) "../public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css";', | ||||
| 				'@import (inline) "../public/vendor/colorpicker/colorpicker.css";', | ||||
| 				'@import (inline) "../node_modules/cropperjs/dist/cropper.css";', | ||||
| 				'@import "../../public/less/flags.less";', | ||||
| 				'@import "../../public/less/blacklist.less";', | ||||
| 				'@import "../../public/less/generics.less";', | ||||
| 				'@import "../../public/less/mixins.less";', | ||||
| 				'@import "../../public/less/global.less";', | ||||
| 			].map(function (str) { | ||||
| 				return str.replace(/\//g, path.sep); | ||||
| 			}).join('\n'); | ||||
| 		}, | ||||
| 		admin: function (source) { | ||||
| 			return source + '\n' + [ | ||||
| 				'@import "font-awesome";', | ||||
| 				'@import "../public/less/admin/admin";', | ||||
| 				'@import "../public/less/generics.less";', | ||||
| 				'@import (inline) "../public/vendor/colorpicker/colorpicker.css";', | ||||
| 				'@import (inline) "../public/vendor/jquery/css/smoothness/jquery-ui.css";', | ||||
| 				'@import (inline) "../public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css";', | ||||
| 				'@import (inline) "../public/vendor/mdl/material.css";', | ||||
| 			].map(function (str) { | ||||
| 				return str.replace(/\//g, path.sep); | ||||
| 			}).join('\n'); | ||||
| 		}, | ||||
| 	}; | ||||
|  | ||||
| 	function filterMissingFiles(filepaths, callback) { | ||||
| 		async.filter(filepaths, function (filepath, next) { | ||||
| 			file.exists(path.join(__dirname, '../../node_modules', filepath), function (err, exists) { | ||||
| 				if (!exists) { | ||||
| 					winston.warn('[meta/css] File not found! ' + filepath); | ||||
| 				} | ||||
|  | ||||
| 				next(err, exists); | ||||
| 			}); | ||||
| 		}, callback); | ||||
| 	} | ||||
|  | ||||
| 	function getImports(files, prefix, extension, callback) { | ||||
| 		var pluginDirectories = []; | ||||
| 		var source = ''; | ||||
|  | ||||
| 		files.forEach(function (styleFile) { | ||||
| 			if (styleFile.endsWith(extension)) { | ||||
| 				source += prefix + path.sep + styleFile + '";'; | ||||
| 			} else { | ||||
| 				pluginDirectories.push(styleFile); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		async.each(pluginDirectories, function (directory, next) { | ||||
| 			file.walk(directory, function (err, styleFiles) { | ||||
| 				if (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
|  | ||||
| 				styleFiles.forEach(function (styleFile) { | ||||
| 					source += prefix + path.sep + styleFile + '";'; | ||||
| 				}); | ||||
|  | ||||
| 				next(); | ||||
| 			}); | ||||
| 		}, function (err) { | ||||
| 			callback(err, source); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function getBundleMetadata(target, callback) { | ||||
| 		var paths = [ | ||||
| 			path.join(__dirname, '../../node_modules'), | ||||
| 			path.join(__dirname, '../../public/vendor/fontawesome/less'), | ||||
| 		]; | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				if (target !== 'client') { | ||||
| 					return next(null, null); | ||||
| 				} | ||||
|  | ||||
| 				db.getObjectFields('config', ['theme:type', 'theme:id'], next); | ||||
| 			}, | ||||
| 			function (themeData, next) { | ||||
| 				if (target === 'client') { | ||||
| 					var themeId = (themeData['theme:id'] || 'nodebb-theme-persona'); | ||||
| 					var baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')); | ||||
| 					paths.unshift(baseThemePath); | ||||
| 				} | ||||
|  | ||||
| 				async.parallel({ | ||||
| 					less: function (cb) { | ||||
| 						async.waterfall([ | ||||
| 							function (next) { | ||||
| 								filterMissingFiles(plugins.lessFiles, next); | ||||
| 							}, | ||||
| 							function (lessFiles, next) { | ||||
| 								getImports(lessFiles, '\n@import ".', '.less', next); | ||||
| 							}, | ||||
| 						], cb); | ||||
| 					}, | ||||
| 					css: function (cb) { | ||||
| 						async.waterfall([ | ||||
| 							function (next) { | ||||
| 								filterMissingFiles(plugins.cssFiles, next); | ||||
| 							}, | ||||
| 							function (cssFiles, next) { | ||||
| 								getImports(cssFiles, '\n@import (inline) ".', '.css', next); | ||||
| 							}, | ||||
| 						], cb); | ||||
| 					}, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (result, next) { | ||||
| 				var cssImports = result.css; | ||||
| 				var lessImports = result.less; | ||||
|  | ||||
| 				var imports = cssImports + '\n' + lessImports; | ||||
| 				imports = buildImports[target](imports); | ||||
|  | ||||
| 				next(null, imports); | ||||
| 			}, | ||||
| 		], function (err, imports) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			callback(null, { paths: paths, imports: imports }); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	Meta.css.buildBundle = function (target, fork, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				getBundleMetadata(target, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				var minify = global.env !== 'development'; | ||||
| 				minifier.css.bundle(data.imports, data.paths, minify, fork, next); | ||||
| 			}, | ||||
| 			function (bundle, next) { | ||||
| 				var filename = (target === 'client' ? 'stylesheet' : 'admin') + '.css'; | ||||
|  | ||||
| 				fs.writeFile(path.join(__dirname, '../../build/public', filename), bundle.code, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
| var buildImports = { | ||||
| 	client: function (source) { | ||||
| 		return '@import "./theme";\n' + source + '\n' + [ | ||||
| 			'@import "font-awesome";', | ||||
| 			'@import (inline) "../public/vendor/jquery/css/smoothness/jquery-ui.css";', | ||||
| 			'@import (inline) "../public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css";', | ||||
| 			'@import (inline) "../public/vendor/colorpicker/colorpicker.css";', | ||||
| 			'@import (inline) "../node_modules/cropperjs/dist/cropper.css";', | ||||
| 			'@import "../../public/less/flags.less";', | ||||
| 			'@import "../../public/less/blacklist.less";', | ||||
| 			'@import "../../public/less/generics.less";', | ||||
| 			'@import "../../public/less/mixins.less";', | ||||
| 			'@import "../../public/less/global.less";', | ||||
| 		].map(function (str) { | ||||
| 			return str.replace(/\//g, path.sep); | ||||
| 		}).join('\n'); | ||||
| 	}, | ||||
| 	admin: function (source) { | ||||
| 		return source + '\n' + [ | ||||
| 			'@import "font-awesome";', | ||||
| 			'@import "../public/less/admin/admin";', | ||||
| 			'@import "../public/less/generics.less";', | ||||
| 			'@import (inline) "../public/vendor/colorpicker/colorpicker.css";', | ||||
| 			'@import (inline) "../public/vendor/jquery/css/smoothness/jquery-ui.css";', | ||||
| 			'@import (inline) "../public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css";', | ||||
| 			'@import (inline) "../public/vendor/mdl/material.css";', | ||||
| 		].map(function (str) { | ||||
| 			return str.replace(/\//g, path.sep); | ||||
| 		}).join('\n'); | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| function filterMissingFiles(filepaths, callback) { | ||||
| 	async.filter(filepaths, function (filepath, next) { | ||||
| 		file.exists(path.join(__dirname, '../../node_modules', filepath), function (err, exists) { | ||||
| 			if (!exists) { | ||||
| 				winston.warn('[meta/css] File not found! ' + filepath); | ||||
| 			} | ||||
|  | ||||
| 			next(err, exists); | ||||
| 		}); | ||||
| 	}, callback); | ||||
| } | ||||
|  | ||||
| function getImports(files, prefix, extension, callback) { | ||||
| 	var pluginDirectories = []; | ||||
| 	var source = ''; | ||||
|  | ||||
| 	files.forEach(function (styleFile) { | ||||
| 		if (styleFile.endsWith(extension)) { | ||||
| 			source += prefix + path.sep + styleFile + '";'; | ||||
| 		} else { | ||||
| 			pluginDirectories.push(styleFile); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	async.each(pluginDirectories, function (directory, next) { | ||||
| 		file.walk(directory, function (err, styleFiles) { | ||||
| 			if (err) { | ||||
| 				return next(err); | ||||
| 			} | ||||
|  | ||||
| 			styleFiles.forEach(function (styleFile) { | ||||
| 				source += prefix + path.sep + styleFile + '";'; | ||||
| 			}); | ||||
|  | ||||
| 			next(); | ||||
| 		}); | ||||
| 	}, function (err) { | ||||
| 		callback(err, source); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function getBundleMetadata(target, callback) { | ||||
| 	var paths = [ | ||||
| 		path.join(__dirname, '../../node_modules'), | ||||
| 		path.join(__dirname, '../../public/vendor/fontawesome/less'), | ||||
| 	]; | ||||
|  | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			if (target !== 'client') { | ||||
| 				return next(null, null); | ||||
| 			} | ||||
|  | ||||
| 			db.getObjectFields('config', ['theme:type', 'theme:id'], next); | ||||
| 		}, | ||||
| 		function (themeData, next) { | ||||
| 			if (target === 'client') { | ||||
| 				var themeId = (themeData['theme:id'] || 'nodebb-theme-persona'); | ||||
| 				var baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')); | ||||
| 				paths.unshift(baseThemePath); | ||||
| 			} | ||||
|  | ||||
| 			async.parallel({ | ||||
| 				less: function (cb) { | ||||
| 					async.waterfall([ | ||||
| 						function (next) { | ||||
| 							filterMissingFiles(plugins.lessFiles, next); | ||||
| 						}, | ||||
| 						function (lessFiles, next) { | ||||
| 							getImports(lessFiles, '\n@import ".', '.less', next); | ||||
| 						}, | ||||
| 					], cb); | ||||
| 				}, | ||||
| 				css: function (cb) { | ||||
| 					async.waterfall([ | ||||
| 						function (next) { | ||||
| 							filterMissingFiles(plugins.cssFiles, next); | ||||
| 						}, | ||||
| 						function (cssFiles, next) { | ||||
| 							getImports(cssFiles, '\n@import (inline) ".', '.css', next); | ||||
| 						}, | ||||
| 					], cb); | ||||
| 				}, | ||||
| 			}, next); | ||||
| 		}, | ||||
| 		function (result, next) { | ||||
| 			var cssImports = result.css; | ||||
| 			var lessImports = result.less; | ||||
|  | ||||
| 			var imports = cssImports + '\n' + lessImports; | ||||
| 			imports = buildImports[target](imports); | ||||
|  | ||||
| 			next(null, imports); | ||||
| 		}, | ||||
| 	], function (err, imports) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		callback(null, { paths: paths, imports: imports }); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| CSS.buildBundle = function (target, fork, callback) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			getBundleMetadata(target, next); | ||||
| 		}, | ||||
| 		function (data, next) { | ||||
| 			var minify = global.env !== 'development'; | ||||
| 			minifier.css.bundle(data.imports, data.paths, minify, fork, next); | ||||
| 		}, | ||||
| 		function (bundle, next) { | ||||
| 			var filename = (target === 'client' ? 'stylesheet' : 'admin') + '.css'; | ||||
|  | ||||
| 			fs.writeFile(path.join(__dirname, '../../build/public', filename), bundle.code, next); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|   | ||||
| @@ -9,73 +9,72 @@ require('colors'); | ||||
|  | ||||
| var pkg = require('../../package.json'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.dependencies = {}; | ||||
| 	var depsMissing = false; | ||||
| 	var depsOutdated = false; | ||||
| var Dependencies = module.exports; | ||||
|  | ||||
| 	Meta.dependencies.check = function (callback) { | ||||
| 		var modules = Object.keys(pkg.dependencies); | ||||
| var depsMissing = false; | ||||
| var depsOutdated = false; | ||||
|  | ||||
| 		winston.verbose('Checking dependencies for outdated modules'); | ||||
| Dependencies.check = function (callback) { | ||||
| 	var modules = Object.keys(pkg.dependencies); | ||||
|  | ||||
| 		async.each(modules, Meta.dependencies.checkModule, function (err) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
| 	winston.verbose('Checking dependencies for outdated modules'); | ||||
|  | ||||
| 			if (depsMissing) { | ||||
| 				callback(new Error('dependencies-missing')); | ||||
| 			} else if (depsOutdated) { | ||||
| 				callback(global.env !== 'development' ? new Error('dependencies-out-of-date') : null); | ||||
| 			} else { | ||||
| 				callback(null); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.dependencies.checkModule = function (moduleName, callback) { | ||||
| 		fs.readFile(path.join(__dirname, '../../node_modules/', moduleName, 'package.json'), { | ||||
| 			encoding: 'utf-8', | ||||
| 		}, function (err, pkgData) { | ||||
| 			if (err) { | ||||
| 				// If a bundled plugin/theme is not present, skip the dep check (#3384) | ||||
| 				if (err.code === 'ENOENT' && (moduleName === 'nodebb-rewards-essentials' || moduleName.startsWith('nodebb-plugin') || moduleName.startsWith('nodebb-theme'))) { | ||||
| 					winston.warn('[meta/dependencies] Bundled plugin ' + moduleName + ' not found, skipping dependency check.'); | ||||
| 					return callback(null, true); | ||||
| 				} | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			pkgData = Meta.dependencies.parseModuleData(moduleName, pkgData); | ||||
|  | ||||
| 			var satisfies = Meta.dependencies.doesSatisfy(pkgData, pkg.dependencies[moduleName]); | ||||
| 			callback(null, satisfies); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.dependencies.parseModuleData = function (moduleName, pkgData) { | ||||
| 		try { | ||||
| 			pkgData = JSON.parse(pkgData); | ||||
| 		} catch (e) { | ||||
| 			winston.warn('[' + 'missing'.red + '] ' + moduleName.bold + ' is a required dependency but could not be found\n'); | ||||
| 			depsMissing = true; | ||||
| 			return null; | ||||
| 	async.each(modules, Dependencies.checkModule, function (err) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
| 		return pkgData; | ||||
| 	}; | ||||
|  | ||||
| 	Meta.dependencies.doesSatisfy = function (moduleData, packageJSONVersion) { | ||||
| 		if (!moduleData) { | ||||
| 			return false; | ||||
| 		if (depsMissing) { | ||||
| 			callback(new Error('dependencies-missing')); | ||||
| 		} else if (depsOutdated) { | ||||
| 			callback(global.env !== 'development' ? new Error('dependencies-out-of-date') : null); | ||||
| 		} else { | ||||
| 			callback(null); | ||||
| 		} | ||||
| 		var versionOk = !semver.validRange(packageJSONVersion) || semver.satisfies(moduleData.version, packageJSONVersion); | ||||
| 		var githubRepo = moduleData._resolved && moduleData._resolved.indexOf('//github.com') !== -1; | ||||
| 		var satisfies = versionOk || githubRepo; | ||||
| 		if (!satisfies) { | ||||
| 			winston.warn('[' + 'outdated'.yellow + '] ' + moduleData.name.bold + ' installed v' + moduleData.version + ', package.json requires ' + packageJSONVersion + '\n'); | ||||
| 			depsOutdated = true; | ||||
| 		} | ||||
| 		return satisfies; | ||||
| 	}; | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| Dependencies.checkModule = function (moduleName, callback) { | ||||
| 	fs.readFile(path.join(__dirname, '../../node_modules/', moduleName, 'package.json'), { | ||||
| 		encoding: 'utf-8', | ||||
| 	}, function (err, pkgData) { | ||||
| 		if (err) { | ||||
| 			// If a bundled plugin/theme is not present, skip the dep check (#3384) | ||||
| 			if (err.code === 'ENOENT' && (moduleName === 'nodebb-rewards-essentials' || moduleName.startsWith('nodebb-plugin') || moduleName.startsWith('nodebb-theme'))) { | ||||
| 				winston.warn('[meta/dependencies] Bundled plugin ' + moduleName + ' not found, skipping dependency check.'); | ||||
| 				return callback(null, true); | ||||
| 			} | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		pkgData = Dependencies.parseModuleData(moduleName, pkgData); | ||||
|  | ||||
| 		var satisfies = Dependencies.doesSatisfy(pkgData, pkg.dependencies[moduleName]); | ||||
| 		callback(null, satisfies); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| Dependencies.parseModuleData = function (moduleName, pkgData) { | ||||
| 	try { | ||||
| 		pkgData = JSON.parse(pkgData); | ||||
| 	} catch (e) { | ||||
| 		winston.warn('[' + 'missing'.red + '] ' + moduleName.bold + ' is a required dependency but could not be found\n'); | ||||
| 		depsMissing = true; | ||||
| 		return null; | ||||
| 	} | ||||
| 	return pkgData; | ||||
| }; | ||||
|  | ||||
| Dependencies.doesSatisfy = function (moduleData, packageJSONVersion) { | ||||
| 	if (!moduleData) { | ||||
| 		return false; | ||||
| 	} | ||||
| 	var versionOk = !semver.validRange(packageJSONVersion) || semver.satisfies(moduleData.version, packageJSONVersion); | ||||
| 	var githubRepo = moduleData._resolved && moduleData._resolved.indexOf('//github.com') !== -1; | ||||
| 	var satisfies = versionOk || githubRepo; | ||||
| 	if (!satisfies) { | ||||
| 		winston.warn('[' + 'outdated'.yellow + '] ' + moduleData.name.bold + ' installed v' + moduleData.version + ', package.json requires ' + packageJSONVersion + '\n'); | ||||
| 		depsOutdated = true; | ||||
| 	} | ||||
| 	return satisfies; | ||||
| }; | ||||
|   | ||||
| @@ -8,61 +8,59 @@ var cronJob = require('cron').CronJob; | ||||
| var db = require('../database'); | ||||
| var analytics = require('../analytics'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.errors = {}; | ||||
| var Errors = module.exports; | ||||
|  | ||||
| 	var counters = {}; | ||||
| var counters = {}; | ||||
|  | ||||
| 	new cronJob('0 * * * * *', function () { | ||||
| 		Meta.errors.writeData(); | ||||
| 	}, null, true); | ||||
| new cronJob('0 * * * * *', function () { | ||||
| 	Errors.writeData(); | ||||
| }, null, true); | ||||
|  | ||||
| 	Meta.errors.writeData = function () { | ||||
| 		var dbQueue = []; | ||||
| 		if (Object.keys(counters).length > 0) { | ||||
| 			for (var key in counters) { | ||||
| 				if (counters.hasOwnProperty(key)) { | ||||
| 					dbQueue.push(async.apply(db.sortedSetIncrBy, 'errors:404', counters[key], key)); | ||||
| 				} | ||||
| Errors.writeData = function () { | ||||
| 	var dbQueue = []; | ||||
| 	if (Object.keys(counters).length > 0) { | ||||
| 		for (var key in counters) { | ||||
| 			if (counters.hasOwnProperty(key)) { | ||||
| 				dbQueue.push(async.apply(db.sortedSetIncrBy, 'errors:404', counters[key], key)); | ||||
| 			} | ||||
| 			counters = {}; | ||||
| 			async.series(dbQueue, function (err) { | ||||
| 				if (err) { | ||||
| 					winston.error(err); | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	Meta.errors.log404 = function (route, callback) { | ||||
| 		callback = callback || function () {}; | ||||
| 		if (!route) { | ||||
| 			return setImmediate(callback); | ||||
| 		} | ||||
| 		route = route.replace(/\/$/, '');	// remove trailing slashes | ||||
| 		analytics.increment('errors:404'); | ||||
| 		counters[route] = counters[route] || 0; | ||||
| 		counters[route] += 1; | ||||
| 		setImmediate(callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.errors.get = function (escape, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.getSortedSetRevRangeWithScores('errors:404', 0, 199, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				data = data.map(function (nfObject) { | ||||
| 					nfObject.value = escape ? validator.escape(String(nfObject.value || '')) : nfObject.value; | ||||
| 					return nfObject; | ||||
| 				}); | ||||
|  | ||||
| 				next(null, data); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.errors.clear = function (callback) { | ||||
| 		db.delete('errors:404', callback); | ||||
| 	}; | ||||
| 		counters = {}; | ||||
| 		async.series(dbQueue, function (err) { | ||||
| 			if (err) { | ||||
| 				winston.error(err); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| Errors.log404 = function (route, callback) { | ||||
| 	callback = callback || function () {}; | ||||
| 	if (!route) { | ||||
| 		return setImmediate(callback); | ||||
| 	} | ||||
| 	route = route.replace(/\/$/, '');	// remove trailing slashes | ||||
| 	analytics.increment('errors:404'); | ||||
| 	counters[route] = counters[route] || 0; | ||||
| 	counters[route] += 1; | ||||
| 	setImmediate(callback); | ||||
| }; | ||||
|  | ||||
| Errors.get = function (escape, callback) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			db.getSortedSetRevRangeWithScores('errors:404', 0, 199, next); | ||||
| 		}, | ||||
| 		function (data, next) { | ||||
| 			data = data.map(function (nfObject) { | ||||
| 				nfObject.value = escape ? validator.escape(String(nfObject.value || '')) : nfObject.value; | ||||
| 				return nfObject; | ||||
| 			}); | ||||
|  | ||||
| 			next(null, data); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| Errors.clear = function (callback) { | ||||
| 	db.delete('errors:404', callback); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										600
									
								
								src/meta/js.js
									
									
									
									
									
								
							
							
						
						
									
										600
									
								
								src/meta/js.js
									
									
									
									
									
								
							| @@ -10,341 +10,339 @@ var file = require('../file'); | ||||
| var plugins = require('../plugins'); | ||||
| var minifier = require('./minifier'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.js = {}; | ||||
| var JS = module.exports; | ||||
|  | ||||
| 	Meta.js.scripts = { | ||||
| 		base: [ | ||||
| 			'node_modules/jquery/dist/jquery.js', | ||||
| 			'node_modules/socket.io-client/dist/socket.io.js', | ||||
| 			'public/vendor/jquery/timeago/jquery.timeago.js', | ||||
| 			'public/vendor/jquery/js/jquery.form.min.js', | ||||
| 			'public/vendor/visibility/visibility.min.js', | ||||
| 			'node_modules/bootstrap/dist/js/bootstrap.js', | ||||
| 			'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js', | ||||
| 			'public/vendor/jquery/textcomplete/jquery.textcomplete.js', | ||||
| 			'public/vendor/requirejs/require.js', | ||||
| 			'public/src/require-config.js', | ||||
| 			'public/vendor/bootbox/bootbox.js', | ||||
| 			'public/vendor/bootbox/wrapper.js', | ||||
| 			'public/vendor/tinycon/tinycon.js', | ||||
| 			'public/vendor/xregexp/xregexp.js', | ||||
| 			'public/vendor/xregexp/unicode/unicode-base.js', | ||||
| 			'node_modules/templates.js/lib/templates.js', | ||||
| 			'public/src/utils.js', | ||||
| 			'public/src/sockets.js', | ||||
| 			'public/src/app.js', | ||||
| 			'public/src/ajaxify.js', | ||||
| 			'public/src/overrides.js', | ||||
| 			'public/src/widgets.js', | ||||
| 			'node_modules/promise-polyfill/promise.js', | ||||
| 		], | ||||
| JS.scripts = { | ||||
| 	base: [ | ||||
| 		'node_modules/jquery/dist/jquery.js', | ||||
| 		'node_modules/socket.io-client/dist/socket.io.js', | ||||
| 		'public/vendor/jquery/timeago/jquery.timeago.js', | ||||
| 		'public/vendor/jquery/js/jquery.form.min.js', | ||||
| 		'public/vendor/visibility/visibility.min.js', | ||||
| 		'node_modules/bootstrap/dist/js/bootstrap.js', | ||||
| 		'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js', | ||||
| 		'public/vendor/jquery/textcomplete/jquery.textcomplete.js', | ||||
| 		'public/vendor/requirejs/require.js', | ||||
| 		'public/src/require-config.js', | ||||
| 		'public/vendor/bootbox/bootbox.js', | ||||
| 		'public/vendor/bootbox/wrapper.js', | ||||
| 		'public/vendor/tinycon/tinycon.js', | ||||
| 		'public/vendor/xregexp/xregexp.js', | ||||
| 		'public/vendor/xregexp/unicode/unicode-base.js', | ||||
| 		'node_modules/templates.js/lib/templates.js', | ||||
| 		'public/src/utils.js', | ||||
| 		'public/src/sockets.js', | ||||
| 		'public/src/app.js', | ||||
| 		'public/src/ajaxify.js', | ||||
| 		'public/src/overrides.js', | ||||
| 		'public/src/widgets.js', | ||||
| 		'node_modules/promise-polyfill/promise.js', | ||||
| 	], | ||||
|  | ||||
| 		// files listed below are only available client-side, or are bundled in to reduce # of network requests on cold load | ||||
| 		rjs: [ | ||||
| 			'public/src/client/footer.js', | ||||
| 			'public/src/client/chats.js', | ||||
| 			'public/src/client/infinitescroll.js', | ||||
| 			'public/src/client/pagination.js', | ||||
| 			'public/src/client/recent.js', | ||||
| 			'public/src/client/unread.js', | ||||
| 			'public/src/client/topic.js', | ||||
| 			'public/src/client/topic/events.js', | ||||
| 			'public/src/client/topic/fork.js', | ||||
| 			'public/src/client/topic/move.js', | ||||
| 			'public/src/client/topic/posts.js', | ||||
| 			'public/src/client/topic/images.js', | ||||
| 			'public/src/client/topic/postTools.js', | ||||
| 			'public/src/client/topic/threadTools.js', | ||||
| 			'public/src/client/categories.js', | ||||
| 			'public/src/client/category.js', | ||||
| 			'public/src/client/category/tools.js', | ||||
| 	// files listed below are only available client-side, or are bundled in to reduce # of network requests on cold load | ||||
| 	rjs: [ | ||||
| 		'public/src/client/footer.js', | ||||
| 		'public/src/client/chats.js', | ||||
| 		'public/src/client/infinitescroll.js', | ||||
| 		'public/src/client/pagination.js', | ||||
| 		'public/src/client/recent.js', | ||||
| 		'public/src/client/unread.js', | ||||
| 		'public/src/client/topic.js', | ||||
| 		'public/src/client/topic/events.js', | ||||
| 		'public/src/client/topic/fork.js', | ||||
| 		'public/src/client/topic/move.js', | ||||
| 		'public/src/client/topic/posts.js', | ||||
| 		'public/src/client/topic/images.js', | ||||
| 		'public/src/client/topic/postTools.js', | ||||
| 		'public/src/client/topic/threadTools.js', | ||||
| 		'public/src/client/categories.js', | ||||
| 		'public/src/client/category.js', | ||||
| 		'public/src/client/category/tools.js', | ||||
|  | ||||
| 			'public/src/modules/translator.js', | ||||
| 			'public/src/modules/notifications.js', | ||||
| 			'public/src/modules/chat.js', | ||||
| 			'public/src/modules/components.js', | ||||
| 			'public/src/modules/sort.js', | ||||
| 			'public/src/modules/navigator.js', | ||||
| 			'public/src/modules/topicSelect.js', | ||||
| 			'public/src/modules/categorySelector.js', | ||||
| 			'public/src/modules/share.js', | ||||
| 			'public/src/modules/search.js', | ||||
| 			'public/src/modules/alerts.js', | ||||
| 			'public/src/modules/taskbar.js', | ||||
| 			'public/src/modules/helpers.js', | ||||
| 			'public/src/modules/string.js', | ||||
| 			'public/src/modules/flags.js', | ||||
| 			'public/src/modules/storage.js', | ||||
| 		], | ||||
| 		'public/src/modules/translator.js', | ||||
| 		'public/src/modules/notifications.js', | ||||
| 		'public/src/modules/chat.js', | ||||
| 		'public/src/modules/components.js', | ||||
| 		'public/src/modules/sort.js', | ||||
| 		'public/src/modules/navigator.js', | ||||
| 		'public/src/modules/topicSelect.js', | ||||
| 		'public/src/modules/categorySelector.js', | ||||
| 		'public/src/modules/share.js', | ||||
| 		'public/src/modules/search.js', | ||||
| 		'public/src/modules/alerts.js', | ||||
| 		'public/src/modules/taskbar.js', | ||||
| 		'public/src/modules/helpers.js', | ||||
| 		'public/src/modules/string.js', | ||||
| 		'public/src/modules/flags.js', | ||||
| 		'public/src/modules/storage.js', | ||||
| 	], | ||||
|  | ||||
| 		// modules listed below are built (/src/modules) so they can be defined anonymously | ||||
| 		modules: { | ||||
| 			'Chart.js': 'node_modules/chart.js/dist/Chart.min.js', | ||||
| 			'mousetrap.js': 'node_modules/mousetrap/mousetrap.min.js', | ||||
| 			'cropper.js': 'node_modules/cropperjs/dist/cropper.min.js', | ||||
| 			'jqueryui.js': 'public/vendor/jquery/js/jquery-ui.js', | ||||
| 			'zxcvbn.js': 'node_modules/zxcvbn/dist/zxcvbn.js', | ||||
| 			ace: 'node_modules/ace-builds/src-min', | ||||
| 		}, | ||||
| 	}; | ||||
| 	// modules listed below are built (/src/modules) so they can be defined anonymously | ||||
| 	modules: { | ||||
| 		'Chart.js': 'node_modules/chart.js/dist/Chart.min.js', | ||||
| 		'mousetrap.js': 'node_modules/mousetrap/mousetrap.min.js', | ||||
| 		'cropper.js': 'node_modules/cropperjs/dist/cropper.min.js', | ||||
| 		'jqueryui.js': 'public/vendor/jquery/js/jquery-ui.js', | ||||
| 		'zxcvbn.js': 'node_modules/zxcvbn/dist/zxcvbn.js', | ||||
| 		ace: 'node_modules/ace-builds/src-min', | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| 	var basePath = path.resolve(__dirname, '../..'); | ||||
| var basePath = path.resolve(__dirname, '../..'); | ||||
|  | ||||
| 	function minifyModules(modules, fork, callback) { | ||||
| 		var moduleDirs = modules.reduce(function (prev, mod) { | ||||
| 			var dir = path.resolve(path.dirname(mod.destPath)); | ||||
| 			if (prev.indexOf(dir) === -1) { | ||||
| 				prev.push(dir); | ||||
| function minifyModules(modules, fork, callback) { | ||||
| 	var moduleDirs = modules.reduce(function (prev, mod) { | ||||
| 		var dir = path.resolve(path.dirname(mod.destPath)); | ||||
| 		if (prev.indexOf(dir) === -1) { | ||||
| 			prev.push(dir); | ||||
| 		} | ||||
| 		return prev; | ||||
| 	}, []); | ||||
|  | ||||
| 	async.eachLimit(moduleDirs, 1000, mkdirp, function (err) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		var filtered = modules.reduce(function (prev, mod) { | ||||
| 			if (mod.srcPath.endsWith('.min.js') || path.dirname(mod.srcPath).endsWith('min')) { | ||||
| 				prev.skip.push(mod); | ||||
| 			} else { | ||||
| 				prev.minify.push(mod); | ||||
| 			} | ||||
|  | ||||
| 			return prev; | ||||
| 		}, []); | ||||
| 		}, { minify: [], skip: [] }); | ||||
|  | ||||
| 		async.eachLimit(moduleDirs, 1000, mkdirp, function (err) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			var filtered = modules.reduce(function (prev, mod) { | ||||
| 				if (mod.srcPath.endsWith('.min.js') || path.dirname(mod.srcPath).endsWith('min')) { | ||||
| 					prev.skip.push(mod); | ||||
| 				} else { | ||||
| 					prev.minify.push(mod); | ||||
| 				} | ||||
|  | ||||
| 				return prev; | ||||
| 			}, { minify: [], skip: [] }); | ||||
|  | ||||
| 			async.parallel([ | ||||
| 				function (cb) { | ||||
| 					minifier.js.minifyBatch(filtered.minify, fork, cb); | ||||
| 				}, | ||||
| 				function (cb) { | ||||
| 					async.eachLimit(filtered.skip, 500, function (mod, next) { | ||||
| 						file.link(mod.srcPath, mod.destPath, next); | ||||
| 					}, cb); | ||||
| 				}, | ||||
| 			], callback); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function linkModules(callback) { | ||||
| 		var modules = Meta.js.scripts.modules; | ||||
|  | ||||
| 		async.eachLimit(Object.keys(modules), 1000, function (relPath, next) { | ||||
| 			var srcPath = path.join(__dirname, '../../', modules[relPath]); | ||||
| 			var destPath = path.join(__dirname, '../../build/public/src/modules', relPath); | ||||
|  | ||||
| 			async.parallel({ | ||||
| 				dir: function (cb) { | ||||
| 					mkdirp(path.dirname(destPath), function (err) { | ||||
| 						cb(err); | ||||
| 					}); | ||||
| 				}, | ||||
| 				stats: function (cb) { | ||||
| 					fs.stat(srcPath, cb); | ||||
| 				}, | ||||
| 			}, function (err, res) { | ||||
| 				if (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
| 				if (res.stats.isDirectory()) { | ||||
| 					return file.linkDirs(srcPath, destPath, next); | ||||
| 				} | ||||
|  | ||||
| 				if (process.platform === 'win32') { | ||||
| 					fs.readFile(srcPath, function (err, file) { | ||||
| 						if (err) { | ||||
| 							return next(err); | ||||
| 						} | ||||
|  | ||||
| 						fs.writeFile(destPath, file, next); | ||||
| 					}); | ||||
| 				} else { | ||||
| 					file.link(srcPath, destPath, next); | ||||
| 				} | ||||
| 			}); | ||||
| 		}, callback); | ||||
| 	} | ||||
|  | ||||
| 	var moduleDirs = ['modules', 'admin', 'client']; | ||||
|  | ||||
| 	function getModuleList(callback) { | ||||
| 		var modules = Object.keys(Meta.js.scripts.modules).map(function (relPath) { | ||||
| 			return { | ||||
| 				srcPath: path.join(__dirname, '../../', Meta.js.scripts.modules[relPath]), | ||||
| 				destPath: path.join(__dirname, '../../build/public/src/modules', relPath), | ||||
| 			}; | ||||
| 		}); | ||||
|  | ||||
| 		var coreDirs = moduleDirs.map(function (dir) { | ||||
| 			return { | ||||
| 				srcPath: path.join(__dirname, '../../public/src', dir), | ||||
| 				destPath: path.join(__dirname, '../../build/public/src', dir), | ||||
| 			}; | ||||
| 		}); | ||||
|  | ||||
| 		modules = modules.concat(coreDirs); | ||||
|  | ||||
| 		var moduleFiles = []; | ||||
| 		async.eachLimit(modules, 1000, function (module, next) { | ||||
| 			var srcPath = module.srcPath; | ||||
| 			var destPath = module.destPath; | ||||
|  | ||||
| 			fs.stat(srcPath, function (err, stats) { | ||||
| 				if (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
| 				if (!stats.isDirectory()) { | ||||
| 					moduleFiles.push(module); | ||||
| 					return next(); | ||||
| 				} | ||||
|  | ||||
| 				file.walk(srcPath, function (err, files) { | ||||
| 					if (err) { | ||||
| 						return next(err); | ||||
| 					} | ||||
|  | ||||
| 					var mods = files.filter(function (filePath) { | ||||
| 						return path.extname(filePath) === '.js'; | ||||
| 					}).map(function (filePath) { | ||||
| 						return { | ||||
| 							srcPath: path.normalize(filePath), | ||||
| 							destPath: path.join(destPath, path.relative(srcPath, filePath)), | ||||
| 						}; | ||||
| 					}); | ||||
|  | ||||
| 					moduleFiles = moduleFiles.concat(mods).map(function (mod) { | ||||
| 						mod.filename = path.relative(basePath, mod.srcPath).replace(/\\/g, '/'); | ||||
| 						return mod; | ||||
| 					}); | ||||
|  | ||||
| 					next(); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, function (err) { | ||||
| 			callback(err, moduleFiles); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function clearModules(callback) { | ||||
| 		var builtPaths = moduleDirs.map(function (p) { | ||||
| 			return path.join(__dirname, '../../build/public/src', p); | ||||
| 		}); | ||||
| 		async.each(builtPaths, function (builtPath, next) { | ||||
| 			rimraf(builtPath, next); | ||||
| 		}, function (err) { | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	Meta.js.buildModules = function (fork, callback) { | ||||
| 		async.waterfall([ | ||||
| 			clearModules, | ||||
| 			function (next) { | ||||
| 				if (global.env === 'development') { | ||||
| 					return linkModules(callback); | ||||
| 				} | ||||
|  | ||||
| 				getModuleList(next); | ||||
| 		async.parallel([ | ||||
| 			function (cb) { | ||||
| 				minifier.js.minifyBatch(filtered.minify, fork, cb); | ||||
| 			}, | ||||
| 			function (modules, next) { | ||||
| 				minifyModules(modules, fork, next); | ||||
| 			function (cb) { | ||||
| 				async.eachLimit(filtered.skip, 500, function (mod, next) { | ||||
| 					file.link(mod.srcPath, mod.destPath, next); | ||||
| 				}, cb); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| 	Meta.js.linkStatics = function (callback) { | ||||
| 		rimraf(path.join(__dirname, '../../build/public/plugins'), function (err) { | ||||
| function linkModules(callback) { | ||||
| 	var modules = JS.scripts.modules; | ||||
|  | ||||
| 	async.eachLimit(Object.keys(modules), 1000, function (relPath, next) { | ||||
| 		var srcPath = path.join(__dirname, '../../', modules[relPath]); | ||||
| 		var destPath = path.join(__dirname, '../../build/public/src/modules', relPath); | ||||
|  | ||||
| 		async.parallel({ | ||||
| 			dir: function (cb) { | ||||
| 				mkdirp(path.dirname(destPath), function (err) { | ||||
| 					cb(err); | ||||
| 				}); | ||||
| 			}, | ||||
| 			stats: function (cb) { | ||||
| 				fs.stat(srcPath, cb); | ||||
| 			}, | ||||
| 		}, function (err, res) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 				return next(err); | ||||
| 			} | ||||
| 			if (res.stats.isDirectory()) { | ||||
| 				return file.linkDirs(srcPath, destPath, next); | ||||
| 			} | ||||
| 			async.eachLimit(Object.keys(plugins.staticDirs), 1000, function (mappedPath, next) { | ||||
| 				var sourceDir = plugins.staticDirs[mappedPath]; | ||||
| 				var destDir = path.join(__dirname, '../../build/public/plugins', mappedPath); | ||||
|  | ||||
| 				mkdirp(path.dirname(destDir), function (err) { | ||||
| 			if (process.platform === 'win32') { | ||||
| 				fs.readFile(srcPath, function (err, file) { | ||||
| 					if (err) { | ||||
| 						return next(err); | ||||
| 					} | ||||
|  | ||||
| 					file.linkDirs(sourceDir, destDir, next); | ||||
| 					fs.writeFile(destPath, file, next); | ||||
| 				}); | ||||
| 			}, callback); | ||||
| 			} else { | ||||
| 				file.link(srcPath, destPath, next); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| 	}, callback); | ||||
| } | ||||
|  | ||||
| 	function getBundleScriptList(target, callback) { | ||||
| 		var pluginDirectories = []; | ||||
| var moduleDirs = ['modules', 'admin', 'client']; | ||||
|  | ||||
| 		if (target === 'admin') { | ||||
| 			target = 'acp'; | ||||
| 		} | ||||
| 		var pluginScripts = plugins[target + 'Scripts'].filter(function (path) { | ||||
| 			if (path.endsWith('.js')) { | ||||
| 				return true; | ||||
| function getModuleList(callback) { | ||||
| 	var modules = Object.keys(JS.scripts.modules).map(function (relPath) { | ||||
| 		return { | ||||
| 			srcPath: path.join(__dirname, '../../', JS.scripts.modules[relPath]), | ||||
| 			destPath: path.join(__dirname, '../../build/public/src/modules', relPath), | ||||
| 		}; | ||||
| 	}); | ||||
|  | ||||
| 	var coreDirs = moduleDirs.map(function (dir) { | ||||
| 		return { | ||||
| 			srcPath: path.join(__dirname, '../../public/src', dir), | ||||
| 			destPath: path.join(__dirname, '../../build/public/src', dir), | ||||
| 		}; | ||||
| 	}); | ||||
|  | ||||
| 	modules = modules.concat(coreDirs); | ||||
|  | ||||
| 	var moduleFiles = []; | ||||
| 	async.eachLimit(modules, 1000, function (module, next) { | ||||
| 		var srcPath = module.srcPath; | ||||
| 		var destPath = module.destPath; | ||||
|  | ||||
| 		fs.stat(srcPath, function (err, stats) { | ||||
| 			if (err) { | ||||
| 				return next(err); | ||||
| 			} | ||||
| 			if (!stats.isDirectory()) { | ||||
| 				moduleFiles.push(module); | ||||
| 				return next(); | ||||
| 			} | ||||
|  | ||||
| 			pluginDirectories.push(path); | ||||
| 			return false; | ||||
| 		}); | ||||
|  | ||||
| 		async.each(pluginDirectories, function (directory, next) { | ||||
| 			file.walk(directory, function (err, scripts) { | ||||
| 			file.walk(srcPath, function (err, files) { | ||||
| 				if (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
|  | ||||
| 				pluginScripts = pluginScripts.concat(scripts); | ||||
| 				var mods = files.filter(function (filePath) { | ||||
| 					return path.extname(filePath) === '.js'; | ||||
| 				}).map(function (filePath) { | ||||
| 					return { | ||||
| 						srcPath: path.normalize(filePath), | ||||
| 						destPath: path.join(destPath, path.relative(srcPath, filePath)), | ||||
| 					}; | ||||
| 				}); | ||||
|  | ||||
| 				moduleFiles = moduleFiles.concat(mods).map(function (mod) { | ||||
| 					mod.filename = path.relative(basePath, mod.srcPath).replace(/\\/g, '/'); | ||||
| 					return mod; | ||||
| 				}); | ||||
|  | ||||
| 				next(); | ||||
| 			}); | ||||
| 		}, function (err) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			var scripts = Meta.js.scripts.base.concat(pluginScripts); | ||||
|  | ||||
| 			if (target === 'client' && global.env !== 'development') { | ||||
| 				scripts = scripts.concat(Meta.js.scripts.rjs); | ||||
| 			} | ||||
|  | ||||
| 			scripts = scripts.map(function (script) { | ||||
| 				var srcPath = path.resolve(basePath, script).replace(/\\/g, '/'); | ||||
| 				return { | ||||
| 					srcPath: srcPath, | ||||
| 					filename: path.relative(basePath, srcPath).replace(/\\/g, '/'), | ||||
| 				}; | ||||
| 			}); | ||||
|  | ||||
| 			callback(null, scripts); | ||||
| 		}); | ||||
| 	} | ||||
| 	}, function (err) { | ||||
| 		callback(err, moduleFiles); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| 	Meta.js.buildBundle = function (target, fork, callback) { | ||||
| 		var fileNames = { | ||||
| 			client: 'nodebb.min.js', | ||||
| 			admin: 'acp.min.js', | ||||
| 		}; | ||||
| function clearModules(callback) { | ||||
| 	var builtPaths = moduleDirs.map(function (p) { | ||||
| 		return path.join(__dirname, '../../build/public/src', p); | ||||
| 	}); | ||||
| 	async.each(builtPaths, function (builtPath, next) { | ||||
| 		rimraf(builtPath, next); | ||||
| 	}, function (err) { | ||||
| 		callback(err); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				getBundleScriptList(target, next); | ||||
| 			}, | ||||
| 			function (files, next) { | ||||
| 				var minify = global.env !== 'development'; | ||||
| 				var filePath = path.join(__dirname, '../../build/public', fileNames[target]); | ||||
| JS.buildModules = function (fork, callback) { | ||||
| 	async.waterfall([ | ||||
| 		clearModules, | ||||
| 		function (next) { | ||||
| 			if (global.env === 'development') { | ||||
| 				return linkModules(callback); | ||||
| 			} | ||||
|  | ||||
| 				minifier.js.bundle({ | ||||
| 					files: files, | ||||
| 					filename: fileNames[target], | ||||
| 					destPath: filePath, | ||||
| 				}, minify, fork, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.js.killMinifier = function () { | ||||
| 		minifier.killAll(); | ||||
| 	}; | ||||
| 			getModuleList(next); | ||||
| 		}, | ||||
| 		function (modules, next) { | ||||
| 			minifyModules(modules, fork, next); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| JS.linkStatics = function (callback) { | ||||
| 	rimraf(path.join(__dirname, '../../build/public/plugins'), function (err) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
| 		async.eachLimit(Object.keys(plugins.staticDirs), 1000, function (mappedPath, next) { | ||||
| 			var sourceDir = plugins.staticDirs[mappedPath]; | ||||
| 			var destDir = path.join(__dirname, '../../build/public/plugins', mappedPath); | ||||
|  | ||||
| 			mkdirp(path.dirname(destDir), function (err) { | ||||
| 				if (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
|  | ||||
| 				file.linkDirs(sourceDir, destDir, next); | ||||
| 			}); | ||||
| 		}, callback); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| function getBundleScriptList(target, callback) { | ||||
| 	var pluginDirectories = []; | ||||
|  | ||||
| 	if (target === 'admin') { | ||||
| 		target = 'acp'; | ||||
| 	} | ||||
| 	var pluginScripts = plugins[target + 'Scripts'].filter(function (path) { | ||||
| 		if (path.endsWith('.js')) { | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		pluginDirectories.push(path); | ||||
| 		return false; | ||||
| 	}); | ||||
|  | ||||
| 	async.each(pluginDirectories, function (directory, next) { | ||||
| 		file.walk(directory, function (err, scripts) { | ||||
| 			if (err) { | ||||
| 				return next(err); | ||||
| 			} | ||||
|  | ||||
| 			pluginScripts = pluginScripts.concat(scripts); | ||||
| 			next(); | ||||
| 		}); | ||||
| 	}, function (err) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		var scripts = JS.scripts.base.concat(pluginScripts); | ||||
|  | ||||
| 		if (target === 'client' && global.env !== 'development') { | ||||
| 			scripts = scripts.concat(JS.scripts.rjs); | ||||
| 		} | ||||
|  | ||||
| 		scripts = scripts.map(function (script) { | ||||
| 			var srcPath = path.resolve(basePath, script).replace(/\\/g, '/'); | ||||
| 			return { | ||||
| 				srcPath: srcPath, | ||||
| 				filename: path.relative(basePath, srcPath).replace(/\\/g, '/'), | ||||
| 			}; | ||||
| 		}); | ||||
|  | ||||
| 		callback(null, scripts); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| JS.buildBundle = function (target, fork, callback) { | ||||
| 	var fileNames = { | ||||
| 		client: 'nodebb.min.js', | ||||
| 		admin: 'acp.min.js', | ||||
| 	}; | ||||
|  | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			getBundleScriptList(target, next); | ||||
| 		}, | ||||
| 		function (files, next) { | ||||
| 			var minify = global.env !== 'development'; | ||||
| 			var filePath = path.join(__dirname, '../../build/public', fileNames[target]); | ||||
|  | ||||
| 			minifier.js.bundle({ | ||||
| 				files: files, | ||||
| 				filename: fileNames[target], | ||||
| 				destPath: filePath, | ||||
| 			}, minify, fork, next); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| JS.killMinifier = function () { | ||||
| 	minifier.killAll(); | ||||
| }; | ||||
|   | ||||
| @@ -3,18 +3,16 @@ | ||||
| var path = require('path'); | ||||
| var fs = require('fs'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.logs = { | ||||
| 		path: path.join(__dirname, '..', '..', 'logs', 'output.log'), | ||||
| 	}; | ||||
| var Logs = module.exports; | ||||
|  | ||||
| 	Meta.logs.get = function (callback) { | ||||
| 		fs.readFile(Meta.logs.path, { | ||||
| 			encoding: 'utf-8', | ||||
| 		}, callback); | ||||
| 	}; | ||||
| Logs.path = path.join(__dirname, '..', '..', 'logs', 'output.log'); | ||||
|  | ||||
| 	Meta.logs.clear = function (callback) { | ||||
| 		fs.truncate(Meta.logs.path, 0, callback); | ||||
| 	}; | ||||
| Logs.get = function (callback) { | ||||
| 	fs.readFile(Logs.path, { | ||||
| 		encoding: 'utf-8', | ||||
| 	}, callback); | ||||
| }; | ||||
|  | ||||
| Logs.clear = function (callback) { | ||||
| 	fs.truncate(Logs.path, 0, callback); | ||||
| }; | ||||
|   | ||||
| @@ -4,61 +4,60 @@ var async = require('async'); | ||||
|  | ||||
| var db = require('../database'); | ||||
| var plugins = require('../plugins'); | ||||
| var Meta = require('../meta'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.settings = {}; | ||||
| var Settings = module.exports; | ||||
|  | ||||
| 	Meta.settings.get = function (hash, callback) { | ||||
| 		db.getObject('settings:' + hash, function (err, settings) { | ||||
| 			callback(err, settings || {}); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.settings.getOne = function (hash, field, callback) { | ||||
| 		db.getObjectField('settings:' + hash, field, callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.settings.set = function (hash, values, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.setObject('settings:' + hash, values, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				plugins.fireHook('action:settings.set', { | ||||
| 					plugin: hash, | ||||
| 					settings: values, | ||||
| 				}); | ||||
|  | ||||
| 				Meta.reloadRequired = true; | ||||
| 				next(); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.settings.setOne = function (hash, field, value, callback) { | ||||
| 		db.setObjectField('settings:' + hash, field, value, callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.settings.setOnEmpty = function (hash, values, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.getObject('settings:' + hash, next); | ||||
| 			}, | ||||
| 			function (settings, next) { | ||||
| 				settings = settings || {}; | ||||
| 				var empty = {}; | ||||
| 				Object.keys(values).forEach(function (key) { | ||||
| 					if (!settings.hasOwnProperty(key)) { | ||||
| 						empty[key] = values[key]; | ||||
| 					} | ||||
| 				}); | ||||
|  | ||||
| 				if (Object.keys(empty).length) { | ||||
| 					db.setObject('settings:' + hash, empty, next); | ||||
| 				} else { | ||||
| 					next(); | ||||
| 				} | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
| Settings.get = function (hash, callback) { | ||||
| 	db.getObject('settings:' + hash, function (err, settings) { | ||||
| 		callback(err, settings || {}); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| Settings.getOne = function (hash, field, callback) { | ||||
| 	db.getObjectField('settings:' + hash, field, callback); | ||||
| }; | ||||
|  | ||||
| Settings.set = function (hash, values, callback) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			db.setObject('settings:' + hash, values, next); | ||||
| 		}, | ||||
| 		function (next) { | ||||
| 			plugins.fireHook('action:settings.set', { | ||||
| 				plugin: hash, | ||||
| 				settings: values, | ||||
| 			}); | ||||
|  | ||||
| 			Meta.reloadRequired = true; | ||||
| 			next(); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| Settings.setOne = function (hash, field, value, callback) { | ||||
| 	db.setObjectField('settings:' + hash, field, value, callback); | ||||
| }; | ||||
|  | ||||
| Settings.setOnEmpty = function (hash, values, callback) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			db.getObject('settings:' + hash, next); | ||||
| 		}, | ||||
| 		function (settings, next) { | ||||
| 			settings = settings || {}; | ||||
| 			var empty = {}; | ||||
| 			Object.keys(values).forEach(function (key) { | ||||
| 				if (!settings.hasOwnProperty(key)) { | ||||
| 					empty[key] = values[key]; | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| 			if (Object.keys(empty).length) { | ||||
| 				db.setObject('settings:' + hash, empty, next); | ||||
| 			} else { | ||||
| 				next(); | ||||
| 			} | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|   | ||||
| @@ -9,125 +9,124 @@ var async = require('async'); | ||||
| var file = require('../file'); | ||||
| var plugins = require('../plugins'); | ||||
| var user = require('../user'); | ||||
| var Meta = require('../meta'); | ||||
|  | ||||
| var soundsPath = path.join(__dirname, '../../build/public/sounds'); | ||||
| var uploadsPath = path.join(__dirname, '../../public/uploads/sounds'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.sounds = {}; | ||||
| var Sounds = module.exports; | ||||
|  | ||||
| 	Meta.sounds.addUploads = function addUploads(callback) { | ||||
| 		fs.readdir(uploadsPath, function (err, files) { | ||||
| 			if (err) { | ||||
| 				if (err.code !== 'ENOENT') { | ||||
| 					return callback(err); | ||||
| 				} | ||||
|  | ||||
| 				files = []; | ||||
| Sounds.addUploads = function addUploads(callback) { | ||||
| 	fs.readdir(uploadsPath, function (err, files) { | ||||
| 		if (err) { | ||||
| 			if (err.code !== 'ENOENT') { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			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); | ||||
| 			files = []; | ||||
| 		} | ||||
|  | ||||
| 				prev[name] = fileName; | ||||
| 		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, | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		callback(); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| Sounds.build = function build(callback) { | ||||
| 	Sounds.addUploads(function (err) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		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; | ||||
| 			}, {}); | ||||
|  | ||||
| 			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, | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			callback(); | ||||
| 		}); | ||||
| 	}; | ||||
| 		map.unshift({}); | ||||
| 		map = Object.assign.apply(null, map); | ||||
|  | ||||
| 	Meta.sounds.build = function build(callback) { | ||||
| 		Meta.sounds.addUploads(function (err) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			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; | ||||
| 				}, {}); | ||||
| 			}); | ||||
| 			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); | ||||
| 				}, | ||||
| 			], function (err) { | ||||
| 				callback(err); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	var keys = ['chat-incoming', 'chat-outgoing', 'notification']; | ||||
|  | ||||
| 	Meta.sounds.getUserSoundMap = function getUserSoundMap(uid, callback) { | ||||
| 		async.parallel({ | ||||
| 			defaultMapping: function (next) { | ||||
| 				Meta.configs.getFields(keys, next); | ||||
| 		async.series([ | ||||
| 			function (next) { | ||||
| 				rimraf(soundsPath, next); | ||||
| 			}, | ||||
| 			userSettings: function (next) { | ||||
| 				user.getSettings(uid, next); | ||||
| 			function (next) { | ||||
| 				mkdirp(soundsPath, next); | ||||
| 			}, | ||||
| 		}, function (err, results) { | ||||
| 			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 = {}; | ||||
|  | ||||
| 			keys.forEach(function (key) { | ||||
| 				if (userSettings[key] || userSettings[key] === '') { | ||||
| 					soundMapping[key] = userSettings[key] || ''; | ||||
| 				} else { | ||||
| 					soundMapping[key] = defaultMapping[key] || ''; | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| 			callback(null, soundMapping); | ||||
| 			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); | ||||
| 			}, | ||||
| 		], function (err) { | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 	}; | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| var keys = ['chat-incoming', 'chat-outgoing', 'notification']; | ||||
|  | ||||
| Sounds.getUserSoundMap = function getUserSoundMap(uid, callback) { | ||||
| 	async.parallel({ | ||||
| 		defaultMapping: function (next) { | ||||
| 			Meta.configs.getFields(keys, next); | ||||
| 		}, | ||||
| 		userSettings: function (next) { | ||||
| 			user.getSettings(uid, next); | ||||
| 		}, | ||||
| 	}, function (err, results) { | ||||
| 		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 = {}; | ||||
|  | ||||
| 		keys.forEach(function (key) { | ||||
| 			if (userSettings[key] || userSettings[key] === '') { | ||||
| 				soundMapping[key] = userSettings[key] || ''; | ||||
| 			} else { | ||||
| 				soundMapping[key] = defaultMapping[key] || ''; | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		callback(null, soundMapping); | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										296
									
								
								src/meta/tags.js
									
									
									
									
									
								
							
							
						
						
									
										296
									
								
								src/meta/tags.js
									
									
									
									
									
								
							| @@ -4,163 +4,163 @@ var nconf = require('nconf'); | ||||
| var validator = require('validator'); | ||||
| var async = require('async'); | ||||
| var winston = require('winston'); | ||||
|  | ||||
| var plugins = require('../plugins'); | ||||
| var Meta = require('../meta'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.tags = {}; | ||||
| var Tags = module.exports; | ||||
|  | ||||
| 	Meta.tags.parse = function (req, meta, link, callback) { | ||||
| 		async.parallel({ | ||||
| 			tags: function (next) { | ||||
| 				var defaultTags = [{ | ||||
| 					name: 'viewport', | ||||
| 					content: 'width=device-width, initial-scale=1.0', | ||||
| 				}, { | ||||
| 					name: 'content-type', | ||||
| 					content: 'text/html; charset=UTF-8', | ||||
| Tags.parse = function (req, meta, link, callback) { | ||||
| 	async.parallel({ | ||||
| 		tags: function (next) { | ||||
| 			var defaultTags = [{ | ||||
| 				name: 'viewport', | ||||
| 				content: 'width=device-width, initial-scale=1.0', | ||||
| 			}, { | ||||
| 				name: 'content-type', | ||||
| 				content: 'text/html; charset=UTF-8', | ||||
| 				noEscape: true, | ||||
| 			}, { | ||||
| 				name: 'apple-mobile-web-app-capable', | ||||
| 				content: 'yes', | ||||
| 			}, { | ||||
| 				name: 'mobile-web-app-capable', | ||||
| 				content: 'yes', | ||||
| 			}, { | ||||
| 				property: 'og:site_name', | ||||
| 				content: Meta.config.title || 'NodeBB', | ||||
| 			}, { | ||||
| 				name: 'msapplication-badge', | ||||
| 				content: 'frequency=30; polling-uri=' + nconf.get('url') + '/sitemap.xml', | ||||
| 				noEscape: true, | ||||
| 			}]; | ||||
|  | ||||
| 			if (Meta.config.keywords) { | ||||
| 				defaultTags.push({ | ||||
| 					name: 'keywords', | ||||
| 					content: Meta.config.keywords, | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			if (Meta.config['brand:logo']) { | ||||
| 				defaultTags.push({ | ||||
| 					name: 'msapplication-square150x150logo', | ||||
| 					content: Meta.config['brand:logo'], | ||||
| 					noEscape: true, | ||||
| 				}, { | ||||
| 					name: 'apple-mobile-web-app-capable', | ||||
| 					content: 'yes', | ||||
| 				}, { | ||||
| 					name: 'mobile-web-app-capable', | ||||
| 					content: 'yes', | ||||
| 				}, { | ||||
| 					property: 'og:site_name', | ||||
| 					content: Meta.config.title || 'NodeBB', | ||||
| 				}, { | ||||
| 					name: 'msapplication-badge', | ||||
| 					content: 'frequency=30; polling-uri=' + nconf.get('url') + '/sitemap.xml', | ||||
| 					noEscape: true, | ||||
| 				}]; | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 				if (Meta.config.keywords) { | ||||
| 					defaultTags.push({ | ||||
| 						name: 'keywords', | ||||
| 						content: Meta.config.keywords, | ||||
| 					}); | ||||
| 				} | ||||
| 			plugins.fireHook('filter:meta.getMetaTags', defaultTags, next); | ||||
| 		}, | ||||
| 		links: function (next) { | ||||
| 			var defaultLinks = [{ | ||||
| 				rel: 'icon', | ||||
| 				type: 'image/x-icon', | ||||
| 				href: nconf.get('relative_path') + '/favicon.ico' + (Meta.config['cache-buster'] ? '?' + Meta.config['cache-buster'] : ''), | ||||
| 			}, { | ||||
| 				rel: 'manifest', | ||||
| 				href: nconf.get('relative_path') + '/manifest.json', | ||||
| 			}]; | ||||
|  | ||||
| 				if (Meta.config['brand:logo']) { | ||||
| 					defaultTags.push({ | ||||
| 						name: 'msapplication-square150x150logo', | ||||
| 						content: Meta.config['brand:logo'], | ||||
| 						noEscape: true, | ||||
| 					}); | ||||
| 				} | ||||
| 			if (plugins.hasListeners('filter:search.query')) { | ||||
| 				defaultLinks.push({ | ||||
| 					rel: 'search', | ||||
| 					type: 'application/opensearchdescription+xml', | ||||
| 					href: nconf.get('relative_path') + '/osd.xml', | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 				plugins.fireHook('filter:meta.getMetaTags', defaultTags, next); | ||||
| 			}, | ||||
| 			links: function (next) { | ||||
| 				var defaultLinks = [{ | ||||
| 			// 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', | ||||
| 					type: 'image/x-icon', | ||||
| 					href: nconf.get('relative_path') + '/favicon.ico' + (Meta.config['cache-buster'] ? '?' + Meta.config['cache-buster'] : ''), | ||||
| 					sizes: '36x36', | ||||
| 					href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-36.png', | ||||
| 				}, { | ||||
| 					rel: 'manifest', | ||||
| 					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({ | ||||
| 						rel: 'apple-touch-icon', | ||||
| 						href: nconf.get('relative_path') + '/apple-touch-icon', | ||||
| 					}, { | ||||
| 						rel: 'icon', | ||||
| 						sizes: '36x36', | ||||
| 						href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-36.png', | ||||
| 					}, { | ||||
| 						rel: 'icon', | ||||
| 						sizes: '48x48', | ||||
| 						href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-48.png', | ||||
| 					}, { | ||||
| 						rel: 'icon', | ||||
| 						sizes: '72x72', | ||||
| 						href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-72.png', | ||||
| 					}, { | ||||
| 						rel: 'icon', | ||||
| 						sizes: '96x96', | ||||
| 						href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-96.png', | ||||
| 					}, { | ||||
| 						rel: 'icon', | ||||
| 						sizes: '144x144', | ||||
| 						href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-144.png', | ||||
| 					}, { | ||||
| 						rel: 'icon', | ||||
| 						sizes: '192x192', | ||||
| 						href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-192.png', | ||||
| 					}); | ||||
| 				} | ||||
| 				plugins.fireHook('filter:meta.getLinkTags', defaultLinks, next); | ||||
| 			}, | ||||
| 		}, function (err, results) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 					rel: 'icon', | ||||
| 					sizes: '48x48', | ||||
| 					href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-48.png', | ||||
| 				}, { | ||||
| 					rel: 'icon', | ||||
| 					sizes: '72x72', | ||||
| 					href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-72.png', | ||||
| 				}, { | ||||
| 					rel: 'icon', | ||||
| 					sizes: '96x96', | ||||
| 					href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-96.png', | ||||
| 				}, { | ||||
| 					rel: 'icon', | ||||
| 					sizes: '144x144', | ||||
| 					href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-144.png', | ||||
| 				}, { | ||||
| 					rel: 'icon', | ||||
| 					sizes: '192x192', | ||||
| 					href: nconf.get('relative_path') + '/assets/uploads/system/touchicon-192.png', | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			meta = results.tags.concat(meta || []).map(function (tag) { | ||||
| 				if (!tag || typeof tag.content !== 'string') { | ||||
| 					winston.warn('Invalid meta tag. ', tag); | ||||
| 					return tag; | ||||
| 				} | ||||
|  | ||||
| 				if (!tag.noEscape) { | ||||
| 					tag.content = validator.escape(String(tag.content)); | ||||
| 				} | ||||
|  | ||||
| 				return tag; | ||||
| 			}); | ||||
|  | ||||
| 			addIfNotExists(meta, 'property', 'og:title', Meta.config.title || 'NodeBB'); | ||||
|  | ||||
| 			var ogUrl = nconf.get('url') + req.path; | ||||
| 			addIfNotExists(meta, 'property', 'og:url', ogUrl); | ||||
|  | ||||
| 			addIfNotExists(meta, 'name', 'description', Meta.config.description); | ||||
| 			addIfNotExists(meta, 'property', 'og:description', Meta.config.description); | ||||
|  | ||||
| 			var ogImage = Meta.config['og:image'] || Meta.config['brand:logo'] || ''; | ||||
| 			if (ogImage && !ogImage.startsWith('http')) { | ||||
| 				ogImage = nconf.get('url') + ogImage; | ||||
| 			} | ||||
| 			addIfNotExists(meta, 'property', 'og:image', ogImage); | ||||
| 			if (ogImage) { | ||||
| 				addIfNotExists(meta, 'property', 'og:image:width', 200); | ||||
| 				addIfNotExists(meta, 'property', 'og:image:height', 200); | ||||
| 			} | ||||
|  | ||||
| 			link = results.links.concat(link || []); | ||||
|  | ||||
| 			callback(null, { | ||||
| 				meta: meta, | ||||
| 				link: link, | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	function addIfNotExists(meta, keyName, tagName, value) { | ||||
| 		var exists = false; | ||||
| 		meta.forEach(function (tag) { | ||||
| 			if (tag[keyName] === tagName) { | ||||
| 				exists = true; | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		if (!exists && value) { | ||||
| 			var data = { | ||||
| 				content: validator.escape(String(value)), | ||||
| 			}; | ||||
| 			data[keyName] = tagName; | ||||
| 			meta.push(data); | ||||
| 			plugins.fireHook('filter:meta.getLinkTags', defaultLinks, next); | ||||
| 		}, | ||||
| 	}, function (err, results) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 		meta = results.tags.concat(meta || []).map(function (tag) { | ||||
| 			if (!tag || typeof tag.content !== 'string') { | ||||
| 				winston.warn('Invalid meta tag. ', tag); | ||||
| 				return tag; | ||||
| 			} | ||||
|  | ||||
| 			if (!tag.noEscape) { | ||||
| 				tag.content = validator.escape(String(tag.content)); | ||||
| 			} | ||||
|  | ||||
| 			return tag; | ||||
| 		}); | ||||
|  | ||||
| 		addIfNotExists(meta, 'property', 'og:title', Meta.config.title || 'NodeBB'); | ||||
|  | ||||
| 		var ogUrl = nconf.get('url') + req.path; | ||||
| 		addIfNotExists(meta, 'property', 'og:url', ogUrl); | ||||
|  | ||||
| 		addIfNotExists(meta, 'name', 'description', Meta.config.description); | ||||
| 		addIfNotExists(meta, 'property', 'og:description', Meta.config.description); | ||||
|  | ||||
| 		var ogImage = Meta.config['og:image'] || Meta.config['brand:logo'] || ''; | ||||
| 		if (ogImage && !ogImage.startsWith('http')) { | ||||
| 			ogImage = nconf.get('url') + ogImage; | ||||
| 		} | ||||
| 		addIfNotExists(meta, 'property', 'og:image', ogImage); | ||||
| 		if (ogImage) { | ||||
| 			addIfNotExists(meta, 'property', 'og:image:width', 200); | ||||
| 			addIfNotExists(meta, 'property', 'og:image:height', 200); | ||||
| 		} | ||||
|  | ||||
| 		link = results.links.concat(link || []); | ||||
|  | ||||
| 		callback(null, { | ||||
| 			meta: meta, | ||||
| 			link: link, | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| function addIfNotExists(meta, keyName, tagName, value) { | ||||
| 	var exists = false; | ||||
| 	meta.forEach(function (tag) { | ||||
| 		if (tag[keyName] === tagName) { | ||||
| 			exists = true; | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	if (!exists && value) { | ||||
| 		var data = { | ||||
| 			content: validator.escape(String(value)), | ||||
| 		}; | ||||
| 		data[keyName] = tagName; | ||||
| 		meta.push(data); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -9,168 +9,167 @@ var async = require('async'); | ||||
|  | ||||
| var file = require('../file'); | ||||
| var db = require('../database'); | ||||
| var Meta = require('../meta'); | ||||
|  | ||||
| module.exports = function (Meta) { | ||||
| 	Meta.themes = {}; | ||||
| var Themes = module.exports; | ||||
|  | ||||
| 	Meta.themes.get = function (callback) { | ||||
| 		var themePath = nconf.get('themes_path'); | ||||
| 		if (typeof themePath !== 'string') { | ||||
| 			return callback(null, []); | ||||
| 		} | ||||
| Themes.get = function (callback) { | ||||
| 	var themePath = nconf.get('themes_path'); | ||||
| 	if (typeof themePath !== 'string') { | ||||
| 		return callback(null, []); | ||||
| 	} | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				fs.readdir(themePath, next); | ||||
| 			}, | ||||
| 			function (files, next) { | ||||
| 				async.filter(files, function (file, next) { | ||||
| 					fs.stat(path.join(themePath, file), function (err, fileStat) { | ||||
| 						if (err) { | ||||
| 							if (err.code === 'ENOENT') { | ||||
| 								return next(null, false); | ||||
| 							} | ||||
| 							return next(err); | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			fs.readdir(themePath, next); | ||||
| 		}, | ||||
| 		function (files, next) { | ||||
| 			async.filter(files, function (file, next) { | ||||
| 				fs.stat(path.join(themePath, file), function (err, fileStat) { | ||||
| 					if (err) { | ||||
| 						if (err.code === 'ENOENT') { | ||||
| 							return next(null, false); | ||||
| 						} | ||||
| 						return next(err); | ||||
| 					} | ||||
|  | ||||
| 						next(null, (fileStat.isDirectory() && file.slice(0, 13) === 'nodebb-theme-')); | ||||
| 					}); | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (themes, next) { | ||||
| 				async.map(themes, function (theme, next) { | ||||
| 					var config = path.join(themePath, theme, 'theme.json'); | ||||
| 					next(null, (fileStat.isDirectory() && file.slice(0, 13) === 'nodebb-theme-')); | ||||
| 				}); | ||||
| 			}, next); | ||||
| 		}, | ||||
| 		function (themes, next) { | ||||
| 			async.map(themes, function (theme, next) { | ||||
| 				var config = path.join(themePath, theme, 'theme.json'); | ||||
|  | ||||
| 					fs.readFile(config, function (err, file) { | ||||
| 						if (err) { | ||||
| 							if (err.code === 'ENOENT') { | ||||
| 								return next(null, null); | ||||
| 							} | ||||
| 							return next(err); | ||||
| 				fs.readFile(config, function (err, file) { | ||||
| 					if (err) { | ||||
| 						if (err.code === 'ENOENT') { | ||||
| 							return next(null, null); | ||||
| 						} | ||||
| 						try { | ||||
| 							var configObj = JSON.parse(file.toString()); | ||||
| 						return next(err); | ||||
| 					} | ||||
| 					try { | ||||
| 						var configObj = JSON.parse(file.toString()); | ||||
|  | ||||
| 							// Minor adjustments for API output | ||||
| 							configObj.type = 'local'; | ||||
| 							if (configObj.screenshot) { | ||||
| 								configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id; | ||||
| 							} else { | ||||
| 								configObj.screenshot_url = nconf.get('relative_path') + '/assets/images/themes/default.png'; | ||||
| 							} | ||||
| 							next(null, configObj); | ||||
| 						} catch (err) { | ||||
| 							winston.error('[themes] Unable to parse theme.json ' + theme); | ||||
| 							next(null, null); | ||||
| 						} | ||||
| 					}); | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (themes, next) { | ||||
| 				themes = themes.filter(Boolean); | ||||
| 				next(null, themes); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.themes.set = function (data, callback) { | ||||
| 		var themeData = { | ||||
| 			'theme:type': data.type, | ||||
| 			'theme:id': data.id, | ||||
| 			'theme:staticDir': '', | ||||
| 			'theme:templates': '', | ||||
| 			'theme:src': '', | ||||
| 		}; | ||||
|  | ||||
| 		switch (data.type) { | ||||
| 		case 'local': | ||||
| 			async.waterfall([ | ||||
| 				async.apply(Meta.configs.get, 'theme:id'), | ||||
| 				function (current, next) { | ||||
| 					async.series([ | ||||
| 						async.apply(db.sortedSetRemove, 'plugins:active', current), | ||||
| 						async.apply(db.sortedSetAdd, 'plugins:active', 0, data.id), | ||||
| 					], function (err) { | ||||
| 						next(err); | ||||
| 					}); | ||||
| 				}, | ||||
| 				function (next) { | ||||
| 					fs.readFile(path.join(nconf.get('themes_path'), data.id, 'theme.json'), function (err, config) { | ||||
| 						if (!err) { | ||||
| 							config = JSON.parse(config.toString()); | ||||
| 							next(null, config); | ||||
| 						// Minor adjustments for API output | ||||
| 						configObj.type = 'local'; | ||||
| 						if (configObj.screenshot) { | ||||
| 							configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id; | ||||
| 						} else { | ||||
| 							next(err); | ||||
| 							configObj.screenshot_url = nconf.get('relative_path') + '/assets/images/themes/default.png'; | ||||
| 						} | ||||
| 					}); | ||||
| 				}, | ||||
| 				function (config, next) { | ||||
| 					themeData['theme:staticDir'] = config.staticDir ? config.staticDir : ''; | ||||
| 					themeData['theme:templates'] = config.templates ? config.templates : ''; | ||||
| 					themeData['theme:src'] = ''; | ||||
| 						next(null, configObj); | ||||
| 					} catch (err) { | ||||
| 						winston.error('[themes] Unable to parse theme.json ' + theme); | ||||
| 						next(null, null); | ||||
| 					} | ||||
| 				}); | ||||
| 			}, next); | ||||
| 		}, | ||||
| 		function (themes, next) { | ||||
| 			themes = themes.filter(Boolean); | ||||
| 			next(null, themes); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| 					Meta.configs.setMultiple(themeData, next); | ||||
|  | ||||
| 					// Re-set the themes path (for when NodeBB is reloaded) | ||||
| 					Meta.themes.setPath(config); | ||||
| 				}, | ||||
| 			], callback); | ||||
|  | ||||
| 			Meta.reloadRequired = true; | ||||
| 			break; | ||||
|  | ||||
| 		case 'bootswatch': | ||||
| 			Meta.configs.setMultiple({ | ||||
| 				'theme:src': data.src, | ||||
| 				bootswatchSkin: data.id.toLowerCase(), | ||||
| 			}, callback); | ||||
| 			break; | ||||
| 		} | ||||
| Themes.set = function (data, callback) { | ||||
| 	var themeData = { | ||||
| 		'theme:type': data.type, | ||||
| 		'theme:id': data.id, | ||||
| 		'theme:staticDir': '', | ||||
| 		'theme:templates': '', | ||||
| 		'theme:src': '', | ||||
| 	}; | ||||
|  | ||||
| 	Meta.themes.setupPaths = function (callback) { | ||||
| 	switch (data.type) { | ||||
| 	case 'local': | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				async.parallel({ | ||||
| 					themesData: Meta.themes.get, | ||||
| 					currentThemeId: function (next) { | ||||
| 						db.getObjectField('config', 'theme:id', next); | ||||
| 					}, | ||||
| 				}, next); | ||||
| 			async.apply(Meta.configs.get, 'theme:id'), | ||||
| 			function (current, next) { | ||||
| 				async.series([ | ||||
| 					async.apply(db.sortedSetRemove, 'plugins:active', current), | ||||
| 					async.apply(db.sortedSetAdd, 'plugins:active', 0, data.id), | ||||
| 				], function (err) { | ||||
| 					next(err); | ||||
| 				}); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				var themeId = data.currentThemeId || 'nodebb-theme-persona'; | ||||
| 			function (next) { | ||||
| 				fs.readFile(path.join(nconf.get('themes_path'), data.id, 'theme.json'), function (err, config) { | ||||
| 					if (!err) { | ||||
| 						config = JSON.parse(config.toString()); | ||||
| 						next(null, config); | ||||
| 					} else { | ||||
| 						next(err); | ||||
| 					} | ||||
| 				}); | ||||
| 			}, | ||||
| 			function (config, next) { | ||||
| 				themeData['theme:staticDir'] = config.staticDir ? config.staticDir : ''; | ||||
| 				themeData['theme:templates'] = config.templates ? config.templates : ''; | ||||
| 				themeData['theme:src'] = ''; | ||||
|  | ||||
| 				var themeObj = data.themesData.filter(function (themeObj) { | ||||
| 					return themeObj.id === themeId; | ||||
| 				})[0]; | ||||
| 				Meta.configs.setMultiple(themeData, next); | ||||
|  | ||||
| 				if (process.env.NODE_ENV === 'development') { | ||||
| 					winston.info('[themes] Using theme ' + themeId); | ||||
| 				} | ||||
|  | ||||
| 				if (!themeObj) { | ||||
| 					return callback(new Error('[[error:theme-not-found]]')); | ||||
| 				} | ||||
|  | ||||
| 				Meta.themes.setPath(themeObj); | ||||
| 				next(); | ||||
| 				// Re-set the themes path (for when NodeBB is reloaded) | ||||
| 				Themes.setPath(config); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Meta.themes.setPath = function (themeObj) { | ||||
| 		// Theme's templates path | ||||
| 		var themePath = nconf.get('base_templates_path'); | ||||
| 		var fallback = path.join(nconf.get('themes_path'), themeObj.id, 'templates'); | ||||
| 		Meta.reloadRequired = true; | ||||
| 		break; | ||||
|  | ||||
| 		if (themeObj.templates) { | ||||
| 			themePath = path.join(nconf.get('themes_path'), themeObj.id, themeObj.templates); | ||||
| 		} else if (file.existsSync(fallback)) { | ||||
| 			themePath = fallback; | ||||
| 		} | ||||
|  | ||||
| 		nconf.set('theme_templates_path', themePath); | ||||
| 		nconf.set('theme_config', path.join(nconf.get('themes_path'), themeObj.id, 'theme.json')); | ||||
| 	}; | ||||
| 	case 'bootswatch': | ||||
| 		Meta.configs.setMultiple({ | ||||
| 			'theme:src': data.src, | ||||
| 			bootswatchSkin: data.id.toLowerCase(), | ||||
| 		}, callback); | ||||
| 		break; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| Themes.setupPaths = function (callback) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
| 			async.parallel({ | ||||
| 				themesData: Themes.get, | ||||
| 				currentThemeId: function (next) { | ||||
| 					db.getObjectField('config', 'theme:id', next); | ||||
| 				}, | ||||
| 			}, next); | ||||
| 		}, | ||||
| 		function (data, next) { | ||||
| 			var themeId = data.currentThemeId || 'nodebb-theme-persona'; | ||||
|  | ||||
| 			var themeObj = data.themesData.filter(function (themeObj) { | ||||
| 				return themeObj.id === themeId; | ||||
| 			})[0]; | ||||
|  | ||||
| 			if (process.env.NODE_ENV === 'development') { | ||||
| 				winston.info('[themes] Using theme ' + themeId); | ||||
| 			} | ||||
|  | ||||
| 			if (!themeObj) { | ||||
| 				return callback(new Error('[[error:theme-not-found]]')); | ||||
| 			} | ||||
|  | ||||
| 			Themes.setPath(themeObj); | ||||
| 			next(); | ||||
| 		}, | ||||
| 	], callback); | ||||
| }; | ||||
|  | ||||
| Themes.setPath = function (themeObj) { | ||||
| 	// Theme's templates path | ||||
| 	var themePath = nconf.get('base_templates_path'); | ||||
| 	var fallback = path.join(nconf.get('themes_path'), themeObj.id, 'templates'); | ||||
|  | ||||
| 	if (themeObj.templates) { | ||||
| 		themePath = path.join(nconf.get('themes_path'), themeObj.id, themeObj.templates); | ||||
| 	} else if (file.existsSync(fallback)) { | ||||
| 		themePath = fallback; | ||||
| 	} | ||||
|  | ||||
| 	nconf.set('theme_templates_path', themePath); | ||||
| 	nconf.set('theme_config', path.join(nconf.get('themes_path'), themeObj.id, 'theme.json')); | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user