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