mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	cover photo for groups, #2588
This commit is contained in:
		| @@ -5,6 +5,11 @@ | |||||||
| 	"new_group": "Create New Group", | 	"new_group": "Create New Group", | ||||||
| 	"no_groups_found": "There are no groups to see", | 	"no_groups_found": "There are no groups to see", | ||||||
|  |  | ||||||
|  | 	"cover-instructions": "Drag and Drop a photo, drag to position, and hit <strong>Save</strong>", | ||||||
|  | 	"cover-change": "Change", | ||||||
|  | 	"cover-save": "Save", | ||||||
|  | 	"cover-saving": "Saving", | ||||||
|  |  | ||||||
| 	"details.title": "Group Details", | 	"details.title": "Group Details", | ||||||
| 	"details.members": "Member List", | 	"details.members": "Member List", | ||||||
| 	"details.pending": "Pending Members", | 	"details.pending": "Pending Members", | ||||||
|   | |||||||
| @@ -1,14 +1,17 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| /* globals define, socket, ajaxify, app, bootbox */ | /* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH */ | ||||||
|  |  | ||||||
| define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker'], function(iconSelect) { | define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect) { | ||||||
| 	var Details = {}; | 	var Details = { | ||||||
|  | 			cover: {} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 	Details.init = function() { | 	Details.init = function() { | ||||||
| 		var detailsPage = $('.groups'), | 		var detailsPage = $('.groups'), | ||||||
| 			settingsFormEl = detailsPage.find('form'); | 			settingsFormEl = detailsPage.find('form'); | ||||||
|  |  | ||||||
| 		Details.prepareSettings(); | 		Details.prepareSettings(); | ||||||
|  | 		Details.initialiseCover(); | ||||||
|  |  | ||||||
| 		$('.latest-posts .content img').addClass('img-responsive'); | 		$('.latest-posts .content img').addClass('img-responsive'); | ||||||
|  |  | ||||||
| @@ -150,5 +153,87 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker'], | |||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | 	Details.initialiseCover = function() { | ||||||
|  | 		var coverEl = $('.group-cover'); | ||||||
|  | 		coverEl.find('.change').on('click', function() { | ||||||
|  | 			coverEl.toggleClass('active', 1); | ||||||
|  | 			coverEl.backgroundDraggable(); | ||||||
|  | 			coverEl.on('dragover', Details.cover.onDragOver); | ||||||
|  | 			coverEl.on('drop', Details.cover.onDrop); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		coverEl.find('.save').on('click', Details.cover.save); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// Cover Photo Handling Code | ||||||
|  |  | ||||||
|  | 	Details.cover.load = function() { | ||||||
|  | 		socket.emit('groups.cover.get', function(err, data) { | ||||||
|  | 			if (!err) { | ||||||
|  | 				var coverEl = $('.group-cover'); | ||||||
|  | 				if (data['cover:url']) { | ||||||
|  | 					coverEl.css('background-image', 'url(' + RELATIVE_PATH + '/theme-rocket/cover/' + data['cover:url'] + ')'); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (data['cover:position']) { | ||||||
|  | 					coverEl.css('background-position', data['cover:position']); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				delete Details.cover.newCover; | ||||||
|  | 			} else { | ||||||
|  | 				app.alertError(err.message); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Details.cover.onDragOver = function(e) { | ||||||
|  | 		e.stopPropagation(); | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 		e.originalEvent.dataTransfer.dropEffect = 'copy'; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Details.cover.onDrop = function(e) { | ||||||
|  | 		var coverEl = $('.group-cover'); | ||||||
|  | 		e.stopPropagation(); | ||||||
|  | 		e.preventDefault(); | ||||||
|  |  | ||||||
|  | 		var files = e.originalEvent.dataTransfer.files, | ||||||
|  | 		reader = new FileReader(); | ||||||
|  |  | ||||||
|  | 		if (files.length && files[0].type.match('image.*')) { | ||||||
|  | 			reader.onload = function(e) { | ||||||
|  | 				coverEl.css('background-image', 'url(' + e.target.result + ')'); | ||||||
|  | 				coverEl.backgroundDraggable(); | ||||||
|  | 				Details.cover.newCover = e.target.result; | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			reader.readAsDataURL(files[0]); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Details.cover.save = function() { | ||||||
|  | 		var coverEl = $('.group-cover'); | ||||||
|  |  | ||||||
|  | 		coverEl.addClass('saving'); | ||||||
|  |  | ||||||
|  | 		socket.emit('groups.cover.update', { | ||||||
|  | 			groupName: ajaxify.variables.get('group_name'), | ||||||
|  | 			imageData: Details.cover.newCover || undefined, | ||||||
|  | 			position: $('.group-cover').css('background-position') | ||||||
|  | 		}, function(err) { | ||||||
|  | 			if (!err) { | ||||||
|  | 				coverEl.toggleClass('active', 0); | ||||||
|  | 				coverEl.backgroundDraggable('disable'); | ||||||
|  | 				coverEl.off('dragover', Details.cover.onDragOver); | ||||||
|  | 				coverEl.off('drop', Details.cover.onDrop); | ||||||
|  | 				Details.cover.load(); | ||||||
|  | 			} else { | ||||||
|  | 				app.alertError(err.message); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			coverEl.removeClass('saving'); | ||||||
|  | 		}); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	return Details; | 	return Details; | ||||||
| }); | }); | ||||||
							
								
								
									
										157
									
								
								public/vendor/jquery/draggable-background/backgroundDraggable.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								public/vendor/jquery/draggable-background/backgroundDraggable.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | |||||||
|  | /** | ||||||
|  |  * Draggable Background plugin for jQuery | ||||||
|  |  * | ||||||
|  |  * v1.2.4 | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014 Kenneth Chung | ||||||
|  |  * | ||||||
|  |  * Licensed under the MIT license: | ||||||
|  |  *   http://www.opensource.org/licenses/mit-license.php | ||||||
|  |  */ | ||||||
|  | ;(function($) { | ||||||
|  |   var $window = $(window); | ||||||
|  |  | ||||||
|  |   // Helper function to guarantee a value between low and hi unless bool is false | ||||||
|  |   var limit = function(low, hi, value, bool) { | ||||||
|  |     if (arguments.length === 3 || bool) { | ||||||
|  |       if (value < low) return low; | ||||||
|  |       if (value > hi) return hi; | ||||||
|  |     } | ||||||
|  |     return value; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // Adds clientX and clientY properties to the jQuery's event object from touch | ||||||
|  |   var modifyEventForTouch = function(e) { | ||||||
|  |     e.clientX = e.originalEvent.touches[0].clientX; | ||||||
|  |     e.clientY = e.originalEvent.touches[0].clientY; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   var getBackgroundImageDimensions = function($el) { | ||||||
|  |     var bgSrc = ($el.css('background-image').match(/^url\(['"]?(.*?)['"]?\)$/i) || [])[1]; | ||||||
|  |     if (!bgSrc) return; | ||||||
|  |  | ||||||
|  |     var imageDimensions = { width: 0, height: 0 }, | ||||||
|  |         image = new Image(); | ||||||
|  |  | ||||||
|  |     image.onload = function() { | ||||||
|  |       if ($el.css('background-size') == "cover") { | ||||||
|  |         var elementWidth = $el.innerWidth(), | ||||||
|  |             elementHeight = $el.innerHeight(), | ||||||
|  |             elementAspectRatio = elementWidth / elementHeight; | ||||||
|  |             imageAspectRatio = image.width / image.height, | ||||||
|  |             scale = 1; | ||||||
|  |  | ||||||
|  |         if (imageAspectRatio >= elementAspectRatio) { | ||||||
|  |           scale = elementHeight / image.height; | ||||||
|  |         } else { | ||||||
|  |           scale = elementWidth / image.width; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         imageDimensions.width = image.width * scale; | ||||||
|  |         imageDimensions.height = image.height * scale; | ||||||
|  |       } else { | ||||||
|  |         imageDimensions.width = image.width; | ||||||
|  |         imageDimensions.height = image.height; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     image.src = bgSrc; | ||||||
|  |  | ||||||
|  |     return imageDimensions; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   function Plugin(element, options) { | ||||||
|  |     this.element = element; | ||||||
|  |     this.options = options; | ||||||
|  |     this.init(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Plugin.prototype.init = function() { | ||||||
|  |     var $el = $(this.element), | ||||||
|  |         bgSrc = ($el.css('background-image').match(/^url\(['"]?(.*?)['"]?\)$/i) || [])[1], | ||||||
|  |         options = this.options; | ||||||
|  |  | ||||||
|  |     if (!bgSrc) return; | ||||||
|  |  | ||||||
|  |     // Get the image's width and height if bound | ||||||
|  |     var imageDimensions = { width: 0, height: 0 }; | ||||||
|  |     if (options.bound) { | ||||||
|  |       imageDimensions = getBackgroundImageDimensions($el); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $el.on('mousedown.dbg touchstart.dbg', function(e) { | ||||||
|  |       if (e.target !== $el[0]) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       e.preventDefault(); | ||||||
|  |  | ||||||
|  |       if (e.originalEvent.touches) { | ||||||
|  |         modifyEventForTouch(e); | ||||||
|  |       } else if (e.which !== 1) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       var x0 = e.clientX, | ||||||
|  |           y0 = e.clientY, | ||||||
|  |           pos = $el.css('background-position').match(/(-?\d+).*?\s(-?\d+)/) || [], | ||||||
|  |           xPos = parseInt(pos[1]) || 0, | ||||||
|  |           yPos = parseInt(pos[2]) || 0; | ||||||
|  |  | ||||||
|  |       $window.on('mousemove.dbg touchmove.dbg', function(e) { | ||||||
|  |         e.preventDefault(); | ||||||
|  |  | ||||||
|  |         if (e.originalEvent.touches) { | ||||||
|  |           modifyEventForTouch(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var x = e.clientX, | ||||||
|  |             y = e.clientY; | ||||||
|  |  | ||||||
|  |         xPos = options.axis === 'y' ? xPos : limit($el.innerWidth()-imageDimensions.width, 0, xPos+x-x0, options.bound); | ||||||
|  |         yPos = options.axis === 'x' ? yPos : limit($el.innerHeight()-imageDimensions.height, 0, yPos+y-y0, options.bound); | ||||||
|  |         x0 = x; | ||||||
|  |         y0 = y; | ||||||
|  |  | ||||||
|  |         $el.css('background-position', xPos + 'px ' + yPos + 'px'); | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       $window.on('mouseup.dbg touchend.dbg mouseleave.dbg', function() { | ||||||
|  |         if (options.done) { | ||||||
|  |           options.done(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $window.off('mousemove.dbg touchmove.dbg'); | ||||||
|  |         $window.off('mouseup.dbg touchend.dbg mouseleave.dbg'); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   Plugin.prototype.disable = function() { | ||||||
|  |     var $el = $(this.element); | ||||||
|  |     $el.off('mousedown.dbg touchstart.dbg'); | ||||||
|  |     $window.off('mousemove.dbg touchmove.dbg mouseup.dbg touchend.dbg mouseleave.dbg'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   $.fn.backgroundDraggable = function(options) { | ||||||
|  |     var options = options; | ||||||
|  |     var args = Array.prototype.slice.call(arguments, 1); | ||||||
|  |  | ||||||
|  |     return this.each(function() { | ||||||
|  |       var $this = $(this); | ||||||
|  |  | ||||||
|  |       if (typeof options == 'undefined' || typeof options == 'object') { | ||||||
|  |         options = $.extend({}, $.fn.backgroundDraggable.defaults, options); | ||||||
|  |         var plugin = new Plugin(this, options); | ||||||
|  |         $this.data('dbg', plugin); | ||||||
|  |       } else if (typeof options == 'string' && $this.data('dbg')) { | ||||||
|  |         var plugin = $this.data('dbg'); | ||||||
|  |         Plugin.prototype[options].apply(plugin, args); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   $.fn.backgroundDraggable.defaults = { | ||||||
|  |     bound: true, | ||||||
|  |     axis: undefined | ||||||
|  |   }; | ||||||
|  | }(jQuery)); | ||||||
| @@ -72,6 +72,10 @@ uploadsController.uploadThumb = function(req, res, next) { | |||||||
| 	}, next); | 	}, next); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | uploadsController.uploadGroupCover = function(data, next) { | ||||||
|  | 	uploadImage(0/*req.user.uid*/, data, next); | ||||||
|  | }; | ||||||
|  |  | ||||||
| function uploadImage(uid, image, callback) { | function uploadImage(uid, image, callback) { | ||||||
| 	if (plugins.hasListeners('filter:uploadImage')) { | 	if (plugins.hasListeners('filter:uploadImage')) { | ||||||
| 		return plugins.fireHook('filter:uploadImage', {image: image, uid: uid}, callback); | 		return plugins.fireHook('filter:uploadImage', {image: image, uid: uid}, callback); | ||||||
|   | |||||||
| @@ -3,14 +3,20 @@ | |||||||
| var async = require('async'), | var async = require('async'), | ||||||
| 	winston = require('winston'), | 	winston = require('winston'), | ||||||
| 	_ = require('underscore'), | 	_ = require('underscore'), | ||||||
|  | 	crypto = require('crypto'), | ||||||
|  | 	path = require('path'), | ||||||
|  | 	nconf = require('nconf'), | ||||||
|  | 	fs = require('fs'), | ||||||
|  |  | ||||||
| 	user = require('./user'), | 	user = require('./user'), | ||||||
| 	meta = require('./meta'), | 	meta = require('./meta'), | ||||||
| 	db = require('./database'), | 	db = require('./database'), | ||||||
| 	plugins = require('./plugins'), | 	plugins = require('./plugins'), | ||||||
| 	posts = require('./posts'), | 	posts = require('./posts'), | ||||||
| 	privileges = require('./privileges'), | 	privileges = require('./privileges'), | ||||||
| 	utils = require('../public/src/utils'); | 	utils = require('../public/src/utils'), | ||||||
|  |  | ||||||
|  | 	uploadsController = require('./controllers/uploads'); | ||||||
|  |  | ||||||
| (function(Groups) { | (function(Groups) { | ||||||
|  |  | ||||||
| @@ -60,7 +66,12 @@ var async = require('async'), | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return groups; | 				return groups; | ||||||
|  | 			}/*, | ||||||
|  | 			fixImageUrl: function(url) { | ||||||
|  | 				if (url) { | ||||||
|  | 					return url.indexOf('http') === -1 ? nconf.get('relative_path') + url : url; | ||||||
| 				} | 				} | ||||||
|  | 			}*/ | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 	Groups.list = function(options, callback) { | 	Groups.list = function(options, callback) { | ||||||
| @@ -185,6 +196,7 @@ var async = require('async'), | |||||||
| 				return callback(err); | 				return callback(err); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// results.base.image = internals.fixImageUrl(results.base.image); | ||||||
| 			results.base.members = results.users.filter(Boolean); | 			results.base.members = results.users.filter(Boolean); | ||||||
| 			results.base.pending = results.pending.filter(Boolean); | 			results.base.pending = results.pending.filter(Boolean); | ||||||
| 			results.base.count = numUsers || results.base.members.length; | 			results.base.count = numUsers || results.base.members.length; | ||||||
| @@ -203,6 +215,15 @@ var async = require('async'), | |||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | 	Groups.getGroupFields = function(groupName, fields, callback) { | ||||||
|  | 		db.getObjectFields('group:' + groupName, fields, callback); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Groups.setGroupField = function(groupName, field, value, callback) { | ||||||
|  | 		plugins.fireHook('action:group.set', {field: field, value: value, type: 'set'}); | ||||||
|  | 		db.setObjectField('group:' + groupName, field, value, callback); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	Groups.isPrivate = function(groupName, callback) { | 	Groups.isPrivate = function(groupName, callback) { | ||||||
| 		db.getObjectField('group:' + groupName, 'private', function(err, isPrivate) { | 		db.getObjectField('group:' + groupName, 'private', function(err, isPrivate) { | ||||||
| 			isPrivate = isPrivate || isPrivate === null; | 			isPrivate = isPrivate || isPrivate === null; | ||||||
| @@ -688,6 +709,63 @@ var async = require('async'), | |||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | 	Groups.updateCoverPosition = function(groupName, position, callback) { | ||||||
|  | 		Groups.setGroupField(groupName, 'cover:position', position, callback); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Groups.updateCover = function(data, callback) { | ||||||
|  | 		var tempPath, md5sum, url; | ||||||
|  |  | ||||||
|  | 		// Position only? That's fine | ||||||
|  | 		if (!data.imageData && data.position) { | ||||||
|  | 			return Groups.updateCoverPosition(data.groupName, data.position, callback); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		async.series([ | ||||||
|  | 			function(next) { | ||||||
|  | 				// Calculate md5sum of image | ||||||
|  | 				// This is required because user data can be private | ||||||
|  | 				md5sum = crypto.createHash('md5'); | ||||||
|  | 				md5sum.update(data.imageData); | ||||||
|  | 				md5sum = md5sum.digest('hex'); | ||||||
|  | 				next(); | ||||||
|  | 			}, | ||||||
|  | 			function(next) { | ||||||
|  | 				// Save image | ||||||
|  | 				tempPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), md5sum); | ||||||
|  | 				var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64'); | ||||||
|  |  | ||||||
|  | 				fs.writeFile(tempPath, buffer, { | ||||||
|  | 					encoding: 'base64' | ||||||
|  | 				}, next); | ||||||
|  | 			}, | ||||||
|  | 			function(next) { | ||||||
|  | 				uploadsController.uploadGroupCover({ | ||||||
|  | 					path: tempPath | ||||||
|  | 				}, function(err, uploadData) { | ||||||
|  | 					if (err) { | ||||||
|  | 						return next(err); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					url = uploadData.url; | ||||||
|  | 					next(); | ||||||
|  | 				}); | ||||||
|  | 			}, | ||||||
|  | 			function(next) { | ||||||
|  | 				Groups.setGroupField(data.groupName, 'cover:url', url, next); | ||||||
|  | 			}, | ||||||
|  | 			function(next) { | ||||||
|  | 				fs.unlink(tempPath, next);	// Delete temporary file | ||||||
|  | 			} | ||||||
|  | 		], function(err) { | ||||||
|  | 			if (err) { | ||||||
|  | 				return callback(err); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			Groups.updateCoverPosition(data.groupName, data.position, callback); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	Groups.ownership = {}; | 	Groups.ownership = {}; | ||||||
|  |  | ||||||
| 	Groups.ownership.isOwner = function(uid, groupName, callback) { | 	Groups.ownership.isOwner = function(uid, groupName, callback) { | ||||||
|   | |||||||
| @@ -126,4 +126,26 @@ SocketGroups.delete = function(socket, data, callback) { | |||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | SocketGroups.cover = {}; | ||||||
|  |  | ||||||
|  | SocketGroups.cover.get = function(socket, data, callback) { | ||||||
|  | 	groups.getGroupFields(data.groupName, ['cover:url', 'cover:position'], callback); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | SocketGroups.cover.update = function(socket, data, callback) { | ||||||
|  | 	if(!data) { | ||||||
|  | 		return callback(new Error('[[error:invalid-data]]')); | ||||||
|  | 	} else if (socket.uid === 0) { | ||||||
|  | 		return callback(new Error('[[error:no-privileges]]')); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) { | ||||||
|  | 		if (!isOwner) { | ||||||
|  | 			return callback(new Error('[[error:no-privileges]]')); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		groups.updateCover(data, callback); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  |  | ||||||
| module.exports = SocketGroups; | module.exports = SocketGroups; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user