diff --git a/app.js b/app.js index d9ac0296e5..2c56822907 100644 --- a/app.js +++ b/app.js @@ -109,11 +109,11 @@ var customTemplates = meta.config['theme:templates'] ? path.join(__dirname, 'node_modules', meta.config['theme:id'], meta.config['theme:templates']) : false; - + utils.walk(path.join(__dirname, 'public/templates'), function (err, tplsToLoad) { templates.init(tplsToLoad, customTemplates); }); - + plugins.ready(function() { templates.ready(webserver.init); }); diff --git a/public/language/en/user.json b/public/language/en/user.json index caa4a59b57..10e7772796 100644 --- a/public/language/en/user.json +++ b/public/language/en/user.json @@ -7,6 +7,7 @@ "location": "Location", "age": "Age", "joined": "Joined", + "lastonline": "Last Online", "profile_views": "Profile views", "reputation": "Reputation", "posts": "Posts", diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index 077a7c3a01..192a707142 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -48,7 +48,6 @@ define(['taskbar'], function(taskbar) { socket.emit('api:composer.push', { pid: pid }, function(threadData) { - console.log(threadData); push({ pid: pid, title: threadData.title, @@ -202,7 +201,7 @@ define(['taskbar'], function(taskbar) { } }); - postContainer.on('click', '.formatting-bar span .fa-picture-o', function() { + postContainer.on('click', '.formatting-bar span .fa-picture-o, .formatting-bar span .fa-upload', function() { $('#files').click(); }); @@ -213,6 +212,7 @@ define(['taskbar'], function(taskbar) { loadFile(post_uuid, files[i]); } } + $('#fileForm')[0].reset(); }); @@ -317,7 +317,12 @@ define(['taskbar'], function(taskbar) { } if(config.imgurClientIDSet) { - postContainer.find('.upload-instructions').removeClass('hide') + postContainer.find('.upload-instructions').removeClass('hide'); + postContainer.find('.img-upload-btn').removeClass('hide'); + } + + if(config.allowFileUploads) { + postContainer.find('.file-upload-btn').removeClass('hide'); } postContainer.css('visibility', 'visible'); @@ -469,36 +474,41 @@ define(['taskbar'], function(taskbar) { } function loadFile(post_uuid, file) { - - if (!file.type.match('image.*')) { - return; - } - var reader = new FileReader(), dropDiv = $('#cmp-uuid-' + post_uuid).find('.imagedrop'); $(reader).on('loadend', function(e) { - var bin = this.result.split(',')[1]; + var regex = /^data:.*;base64,(.*)$/; + console.log(file); + var matches = this.result.match(regex); - var img = { + var fileData = { name: file.name, - data: bin + data: matches[1] }; - createImagePlaceholder(post_uuid, img); - dropDiv.hide(); + + if(file.type.match('image.*')) { + uploadFile('api:posts.uploadImage', post_uuid, fileData); + } else { + if(file.size > parseInt(config.maximumFileSize, 10) * 1024) { + return composerAlert('File too big', 'Maximum allowed file size is ' + config.maximumFileSize + 'kbs'); + } + uploadFile('api:posts.uploadFile', post_uuid, fileData); + } }); reader.readAsDataURL(file); } - function createImagePlaceholder(post_uuid, img) { - var postContainer = $('#cmp-uuid-' + post_uuid), + function uploadFile(method, post_uuid, img) { + var linkStart = method === 'api:posts.uploadImage' ? '!' : '', + postContainer = $('#cmp-uuid-' + post_uuid), textarea = postContainer.find('textarea'), text = textarea.val(), - imgText = "![" + img.name + "](uploading...)"; + imgText = linkStart + '[' + img.name + '](uploading...)'; text += imgText; textarea.val(text + " "); @@ -509,18 +519,17 @@ define(['taskbar'], function(taskbar) { composer.posts[post_uuid].uploadsInProgress.push(1); - socket.emit("api:posts.uploadImage", img, function(err, data) { + socket.emit(method, img, function(err, data) { + + var currentText = textarea.val(); + if(err) { + textarea.val(currentText.replace(imgText, linkStart + '[' + img.name + '](upload error)')); return app.alertError(err.message); } - var currentText = textarea.val(); - imgText = "![" + data.name + "](uploading...)"; - if(!err) { - textarea.val(currentText.replace(imgText, "![" + data.name + "](" + data.url + ")")); - } else { - textarea.val(currentText.replace(imgText, "![" + data.name + "](upload error)")); - } + textarea.val(currentText.replace(imgText, linkStart + '[' + data.name + '](' + data.url + ')')); + composer.posts[post_uuid].uploadsInProgress.pop(); }); } diff --git a/public/templates/account.tpl b/public/templates/account.tpl index f54b52cb95..e1ddded78a 100644 --- a/public/templates/account.tpl +++ b/public/templates/account.tpl @@ -70,6 +70,10 @@
+ [[user:lastonline]] + +
+ [[user:profile_views]] {profileviews}
diff --git a/public/templates/admin/settings.tpl b/public/templates/admin/settings.tpl index c2e7463410..5f334cad02 100644 --- a/public/templates/admin/settings.tpl +++ b/public/templates/admin/settings.tpl @@ -121,6 +121,17 @@ Use Outgoing Links Warning Page +
+ +
+
+ +
+ Maximum File Size

diff --git a/public/templates/composer.tpl b/public/templates/composer.tpl index 8ad2cc533d..6a53fd7ba2 100644 --- a/public/templates/composer.tpl +++ b/public/templates/composer.tpl @@ -7,10 +7,15 @@ - - + + + + +
+ +
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl index f6c96e0844..04fe730113 100644 --- a/public/templates/topic.tpl +++ b/public/templates/topic.tpl @@ -86,9 +86,11 @@
+ +
diff --git a/src/database/mongo.js b/src/database/mongo.js index f9b6ee8eea..9f1f8ea6a9 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -50,13 +50,20 @@ function createCollections() { db.createCollection('objects', function(err, collection) { if(err) { - winston.error("Error creating collection " + err.message); + winston.error('Error creating collection ' + err.message); return; } + if(collection) { - collection.ensureIndex({_key :1}, {background:true}, function(err, name){ + collection.ensureIndex({_key :1}, {background:true}, function(err, name) { + if(err) { + winston.error('Error creating index ' + err.message); + } + }); + + collection.ensureIndex({'expireAt':1}, {expireAfterSeconds:0, background:true}, function(err, name) { if(err) { - winston.error("Error creating index " + err.message); + winston.error('Error creating index ' + err.message); } }); } @@ -64,13 +71,13 @@ db.createCollection('search', function(err, collection) { if(err) { - winston.error("Error creating collection " + err.message); + winston.error('Error creating collection ' + err.message); return; } if(collection) { collection.ensureIndex({content:'text'}, {background:true}, function(err, name){ if(err) { - winston.error("Error creating index " + err.message); + winston.error('Error creating index ' + err.message); } }); } @@ -241,6 +248,14 @@ }); } + module.expire = function(key, seconds, callback) { + module.expireAt(key, Math.round(Date.now() / 1000) + seconds, callback); + } + + module.expireAt = function(key, timestamp, callback) { + module.setObjectField(key, 'expireAt', new Date(timestamp * 1000), callback); + } + //hashes module.setObject = function(key, data, callback) { data['_key'] = key; diff --git a/src/database/redis.js b/src/database/redis.js index 02941be081..a298980fef 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -211,6 +211,14 @@ redisClient.keys(key, callback); } + module.expire = function(key, seconds, callback) { + redisClient.expire(key, seconds, callback); + } + + module.expireAt = function(key, timestamp, callback) { + redisClient.expireat(key, timestamp, callback); + } + //hashes module.setObject = function(key, data, callback) { diff --git a/src/imgur.js b/src/imgur.js index 389a3e3b37..84f6c898ee 100644 --- a/src/imgur.js +++ b/src/imgur.js @@ -20,6 +20,7 @@ var request = require('request'), try { var response = JSON.parse(body); + if(response.success) { callback(null, response.data); } else { diff --git a/src/install.js b/src/install.js index dbbb748f01..e5c64156a4 100644 --- a/src/install.js +++ b/src/install.js @@ -212,6 +212,12 @@ var async = require('async'), }, { field: 'allowRegistration', value: 1 + }, { + field: 'allowFileUploads', + value: 0, + }, { + filed: 'maximumFileSize', + value: 2048 }, { field: 'minimumTitleLength', value: 3 diff --git a/src/posts.js b/src/posts.js index 200af7c452..0eb19b4a24 100644 --- a/src/posts.js +++ b/src/posts.js @@ -12,6 +12,8 @@ var db = require('./database'), meta = require('./meta'), async = require('async'), + path = require('path'), + fs = require('fs'), nconf = require('nconf'), validator = require('validator'), winston = require('winston'), @@ -358,6 +360,10 @@ var db = require('./database'), Posts.uploadPostImage = function(image, callback) { + if(!meta.config.imgurClientID) { + return callback('imgurClientID not set', null); + } + if(!image) { return callback('invalid image', null); } @@ -374,6 +380,37 @@ var db = require('./database'), }); } + Posts.uploadPostFile = function(file, callback) { + + if(!meta.config.allowFileUploads) { + return callback('File uploads are not allowed'); + } + + if(!file) { + return callback('invalid file'); + } + + var buffer = new Buffer(file.data, 'base64'); + + if(buffer.length > parseInt(meta.config.maximumFileSize, 10) * 1024) { + return callback('File too big'); + } + + var filename = 'upload-' + utils.generateUUID() + path.extname(file.name); + var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename); + + fs.writeFile(uploadPath, buffer, function (err) { + if(err) { + callback(err.message, null); + } else { + callback(null, { + url: nconf.get('upload_url') + filename, + name: file.name + }); + } + }); + } + Posts.getPostsByUid = function(uid, start, end, callback) { user.getPostIds(uid, start, end, function(err, pids) { if(err) { diff --git a/src/routes/api.js b/src/routes/api.js index 623d654fbd..3f0dbc270c 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -40,6 +40,8 @@ var path = require('path'), config.useOutgoingLinksPage = meta.config.useOutgoingLinksPage; config.allowGuestPosting = meta.config.allowGuestPosting; config.allowRegistration = meta.config.allowRegistration || '1'; + config.allowFileUploads = meta.config.allowFileUploads; + config.maximumFileSize = meta.config.maximumFileSize; config.emailSetup = !!meta.config['email:from']; res.json(200, config); diff --git a/src/routes/user.js b/src/routes/user.js index 543bbb5162..dd5491b43e 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -530,6 +530,11 @@ var fs = require('fs'), user.getUserData(uid, function (err, data) { if (data) { data.joindate = new Date(parseInt(data.joindate, 10)).toISOString(); + if(data.lastonline) { + data.lastonline = new Date(parseInt(data.lastonline, 10)).toISOString(); + } else { + data.lastonline = data.joindate; + } if (!data.birthday) { data.age = ''; diff --git a/src/topics.js b/src/topics.js index 66af3845d3..751086ae68 100644 --- a/src/topics.js +++ b/src/topics.js @@ -667,6 +667,7 @@ var async = require('async'), 'unreplied': parseInt(topicData.postcount, 10) > 1, 'topic_id': tid, 'expose_tools': privileges.editable ? 1 : 0, + 'disableSocialButtons': meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false, 'posts': topicPosts }); }); diff --git a/src/upgrade.js b/src/upgrade.js index f526491398..bf70ec9348 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -14,7 +14,7 @@ var db = require('./database'), Upgrade.check = function(callback) { // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - var latestSchema = new Date(2013, 11, 31).getTime(); + var latestSchema = new Date(2014, 0, 1).getTime(); db.get('schemaDate', function(err, value) { if (parseInt(value, 10) >= latestSchema) { diff --git a/src/webserver.js b/src/webserver.js index 48fb5ac17c..86e1a830db 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -188,6 +188,13 @@ var path = require('path'), // Authentication Routes auth.initialize(app); + app.use(function(req, res, next) { + if(req.user) { + user.setUserField(req.user.uid, 'lastonline', Date.now()); + } + next(); + }) + next(); }, function(next) { diff --git a/src/websockets.js b/src/websockets.js index 1a16b76af7..b435d4b6ec 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -638,6 +638,10 @@ websockets.init = function(io) { posts.uploadPostImage(data, callback); }); + socket.on('api:posts.uploadFile', function(data, callback) { + posts.uploadPostFile(data, callback); + }); + socket.on('api:posts.getRawPost', function(data, callback) { posts.getPostField(data.pid, 'content', function(err, raw) { callback({