Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7955a5d53a | ||
|
|
c261babf17 | ||
|
|
b90eef6d19 | ||
|
|
5c597ca218 | ||
|
|
3dbcf8112d | ||
|
|
5357ad61db | ||
|
|
ff50917c29 | ||
|
|
48835d8c44 | ||
|
|
e9c66bb35a | ||
|
|
23eb7824ac | ||
|
|
494b9d23ac | ||
|
|
64ae9ac033 | ||
|
|
a72fc69997 | ||
|
|
a16f93cbd5 | ||
|
|
81e5cf0cf3 | ||
|
|
01102d5982 | ||
|
|
2174aec0e1 | ||
|
|
46bad118de | ||
|
|
2d7228fa40 | ||
|
|
a1839d90fd | ||
|
|
0cc136c3f6 | ||
|
|
cd1e26418d | ||
|
|
dab4f07258 | ||
|
|
501dc56fd3 | ||
|
|
253271127d | ||
|
|
f2da892b38 | ||
|
|
6dd72f480c | ||
|
|
3caf8b4a67 | ||
|
|
39f2efbef8 | ||
|
|
288341945d | ||
|
|
d02bd72764 | ||
|
|
7d3adb9275 | ||
|
|
156950ac2f | ||
|
|
83f18c1915 | ||
|
|
332730575f | ||
|
|
08ef67e824 | ||
|
|
7e71fb218c | ||
|
|
a7216caa3b | ||
|
|
87309601ce | ||
|
|
53db9db50f | ||
|
|
23628668b7 | ||
|
|
6ac685b194 | ||
|
|
db8c43ca97 | ||
|
|
bff0c8fdaf | ||
|
|
6f2b809385 | ||
|
|
455479bd54 | ||
|
|
03b34a449d | ||
|
|
08e51c8942 | ||
|
|
4aef5bfb72 | ||
|
|
34c74770ce | ||
|
|
da8d198676 | ||
|
|
33868804fd | ||
|
|
22a3794c51 | ||
|
|
1058d54c52 | ||
|
|
90ce539683 | ||
|
|
99c2fbd947 | ||
|
|
866d813218 | ||
|
|
f1df8c2479 | ||
|
|
6f1523c279 | ||
|
|
163cdaf70c | ||
|
|
a9ce8393e4 | ||
|
|
f04e30c4d4 | ||
|
|
9d0f8b4543 | ||
|
|
d631a4b2e5 | ||
|
|
2cf55dcf9f | ||
|
|
9fbb139e67 | ||
|
|
11e3b0da7d | ||
|
|
0b922d3f60 | ||
|
|
7e4faa3270 | ||
|
|
635fba1e45 | ||
|
|
7c950cc350 | ||
|
|
cc0fe66e3e | ||
|
|
b2d6ce59cf | ||
|
|
586a181e0a | ||
|
|
33150943df | ||
|
|
28dab60232 | ||
|
|
71ef76b108 | ||
|
|
64008ef5d8 | ||
|
|
1859154370 | ||
|
|
fa4067e885 | ||
|
|
c8c355b319 | ||
|
|
51355a53d9 | ||
|
|
1f3f672d3f | ||
|
|
7c3fa30c13 | ||
|
|
cbbb7a7c8e | ||
|
|
6893bd8b04 | ||
|
|
22eabf6620 | ||
|
|
a827888ee3 | ||
|
|
54d94f5988 | ||
|
|
7c1b6d6ad2 | ||
|
|
8c4f776122 | ||
|
|
84fa704b25 | ||
|
|
535379d9d7 | ||
|
|
d2927e2be2 | ||
|
|
bd04b2f921 |
5
.gitignore
vendored
@@ -11,4 +11,7 @@ public/css/*.css
|
||||
*.swp
|
||||
Vagrantfile
|
||||
.vagrant
|
||||
provision.sh
|
||||
provision.sh
|
||||
*.komodoproject
|
||||
|
||||
feeds/recent.rss
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## How can I follow along/contribute?
|
||||
|
||||
|
||||
5
app.js
@@ -64,6 +64,7 @@
|
||||
|
||||
nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path') + '/');
|
||||
nconf.set('upload_url', nconf.get('url') + 'uploads/');
|
||||
nconf.set('base_dir', __dirname);
|
||||
|
||||
winston.info('Initializing NodeBB v' + pkg.version + ', on port ' + nconf.get('port') + ', using Redis store at ' + nconf.get('redis:host') + ':' + nconf.get('redis:port') + '.');
|
||||
winston.info('NodeBB instance bound to: ' + ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? 'Any address (0.0.0.0)' : nconf.get('bind_address')));
|
||||
@@ -112,7 +113,9 @@
|
||||
], customTemplates);
|
||||
|
||||
|
||||
templates.ready(webserver.init);
|
||||
plugins.ready(function() {
|
||||
templates.ready(webserver.init);
|
||||
});
|
||||
|
||||
Notifications.init();
|
||||
} else {
|
||||
|
||||
4
nodebb
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
clear
|
||||
echo "Launching NodeBB in \"development\" mode."
|
||||
echo "To run the production build of NodeBB, please use \"forever\"."
|
||||
echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
|
||||
NODE_ENV=development supervisor --extensions 'node|js|tpl' -- app $1
|
||||
NODE_ENV=development supervisor --extensions 'node|js|tpl' -- app $1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPLv3 or later",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"homepage": "http://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -43,7 +43,7 @@
|
||||
"nodebb-plugin-mentions": "~0.1.14",
|
||||
"nodebb-plugin-markdown": "~0.1.8",
|
||||
"nodebb-theme-vanilla": "designcreateplay/nodebb-theme-vanilla",
|
||||
"nodebb-theme-cerulean": "0.0.6",
|
||||
"nodebb-theme-cerulean": "0.0.7",
|
||||
"cron": "~1.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var ajaxify = {};
|
||||
"use strict";
|
||||
|
||||
var ajaxify = {};
|
||||
|
||||
(function ($) {
|
||||
/*global app, templates, utils*/
|
||||
@@ -23,8 +24,9 @@ var ajaxify = {};
|
||||
|
||||
window.onpopstate = function (event) {
|
||||
// "quiet": If set to true, will not call pushState
|
||||
if (event !== null && event.state && event.state.url !== undefined)
|
||||
if (event !== null && event.state && event.state.url !== undefined) {
|
||||
ajaxify.go(event.state.url, null, null, true);
|
||||
}
|
||||
};
|
||||
|
||||
var pagination;
|
||||
@@ -32,10 +34,13 @@ var ajaxify = {};
|
||||
ajaxify.go = function (url, callback, template, quiet) {
|
||||
// start: the following should be set like so: ajaxify.onchange(function(){}); where the code actually belongs
|
||||
$(window).off('scroll');
|
||||
app.enter_room('global');
|
||||
app.enterRoom('global');
|
||||
|
||||
pagination = pagination || document.getElementById('pagination');
|
||||
if (pagination) pagination.parentNode.style.display = 'none';
|
||||
if (pagination) {
|
||||
pagination.parentNode.style.display = 'none';
|
||||
}
|
||||
|
||||
window.onscroll = null;
|
||||
// end
|
||||
|
||||
@@ -62,13 +67,26 @@ var ajaxify = {};
|
||||
}
|
||||
|
||||
if (templates.is_available(tpl_url) && !templates.force_refresh(tpl_url)) {
|
||||
if (quiet !== true) {
|
||||
//if (quiet !== true) {
|
||||
if (window.history && window.history.pushState) {
|
||||
window.history.pushState({
|
||||
"url": url
|
||||
}, url, RELATIVE_PATH + "/" + url);
|
||||
url: url
|
||||
}, url, RELATIVE_PATH + '/' + url);
|
||||
|
||||
$.ajax(RELATIVE_PATH + '/plugins/fireHook', {
|
||||
type: 'PUT',
|
||||
data: {
|
||||
_csrf: $('#csrf_token').val(),
|
||||
hook: 'page.load',
|
||||
args: {
|
||||
template: tpl_url,
|
||||
url: url,
|
||||
uid: app.uid
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
translator.load(tpl_url);
|
||||
|
||||
@@ -78,20 +96,27 @@ var ajaxify = {};
|
||||
templates.load_template(function () {
|
||||
exec_body_scripts(content);
|
||||
require(['forum/' + tpl_url], function(script) {
|
||||
if (script && script.init) script.init();
|
||||
if (script && script.init) {
|
||||
script.init();
|
||||
}
|
||||
});
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
app.process_page();
|
||||
app.processPage();
|
||||
|
||||
jQuery('#content, #footer').stop(true, true).fadeIn(200, function () {
|
||||
if (window.location.hash)
|
||||
if (window.location.hash) {
|
||||
hash = window.location.hash;
|
||||
if (hash)
|
||||
app.scrollToPost(hash.substr(1));
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
require(['forum/topic'], function(topic) {
|
||||
topic.scrollToPost(hash.substr(1))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
utils.refreshTitle(url);
|
||||
@@ -105,23 +130,27 @@ var ajaxify = {};
|
||||
};
|
||||
|
||||
$('document').ready(function () {
|
||||
if (!window.history || !window.history.pushState) return; // no ajaxification for old browsers
|
||||
if (!window.history || !window.history.pushState) {
|
||||
return; // no ajaxification for old browsers
|
||||
}
|
||||
|
||||
content = content || document.getElementById('content');
|
||||
|
||||
// Enhancing all anchors to ajaxify...
|
||||
$(document.body).on('click', 'a', function (e) {
|
||||
function hrefEmpty(href) {
|
||||
return href == 'javascript:;' || href == window.location.href + "#" || href.slice(-1) === "#";
|
||||
return href === 'javascript:;' || href === window.location.href + "#" || href.slice(-1) === "#";
|
||||
}
|
||||
|
||||
if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:')
|
||||
if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:') {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!window.location.pathname.match(/\/(403|404)$/g))
|
||||
if(!window.location.pathname.match(/\/(403|404)$/g)) {
|
||||
app.previousUrl = window.location.href;
|
||||
}
|
||||
|
||||
if (this.getAttribute('data-ajaxify') == 'false') {
|
||||
if (this.getAttribute('data-ajaxify') === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ var socket,
|
||||
setTimeout(app.logout, 1000);
|
||||
});
|
||||
|
||||
app.enter_room('global');
|
||||
app.enterRoom('global');
|
||||
}
|
||||
},
|
||||
async: false
|
||||
@@ -186,43 +186,35 @@ var socket,
|
||||
clearTimeout(alert.attr('timeoutId'));
|
||||
startTimeout(alert, params.timeout);
|
||||
} else {
|
||||
var div = document.createElement('div'),
|
||||
button = document.createElement('button'),
|
||||
strong = document.createElement('strong'),
|
||||
p = document.createElement('p');
|
||||
var div = $('<div id="' + alert_id + '" class="alert toaster-alert alert-' + params.type +'"></div>'),
|
||||
button = $('<button class="close">×</button>'),
|
||||
strong = $('<strong>' + title + '</strong>'),
|
||||
p = $('<p>' + params.message + '</p>');
|
||||
|
||||
p.innerHTML = params.message;
|
||||
strong.innerHTML = title;
|
||||
div.append(button)
|
||||
.append(strong)
|
||||
.append(p);
|
||||
|
||||
div.className = "alert toaster-alert " + "alert-" + params.type;
|
||||
|
||||
div.setAttribute('id', alert_id);
|
||||
div.appendChild(button);
|
||||
div.appendChild(strong);
|
||||
div.appendChild(p);
|
||||
|
||||
button.className = 'close';
|
||||
button.innerHTML = '×';
|
||||
button.onclick = function (ev) {
|
||||
div.parentNode.removeChild(div);
|
||||
}
|
||||
button.on('click', function () {
|
||||
div.remove();
|
||||
});
|
||||
|
||||
if (params.location == null)
|
||||
params.location = 'alert_window';
|
||||
|
||||
jQuery('#' + params.location).prepend(jQuery(div).fadeIn('100'));
|
||||
$('#' + params.location).prepend(div.fadeIn('100'));
|
||||
|
||||
if (params.timeout) {
|
||||
startTimeout(div, params.timeout);
|
||||
}
|
||||
|
||||
if (params.clickfn) {
|
||||
div.onclick = function () {
|
||||
div.on('click', function () {
|
||||
params.clickfn();
|
||||
jQuery(div).fadeOut(500, function () {
|
||||
this.remove();
|
||||
div.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,22 +243,23 @@ var socket,
|
||||
});
|
||||
}
|
||||
|
||||
app.current_room = null;
|
||||
app.enter_room = function (room) {
|
||||
app.currentRoom = null;
|
||||
app.enterRoom = function (room) {
|
||||
if (socket) {
|
||||
if (app.current_room === room)
|
||||
if (app.currentRoom === room) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.emit('event:enter_room', {
|
||||
'enter': room,
|
||||
'leave': app.current_room
|
||||
'leave': app.currentRoom
|
||||
});
|
||||
|
||||
app.current_room = room;
|
||||
app.currentRoom = room;
|
||||
}
|
||||
};
|
||||
|
||||
app.populate_online_users = function () {
|
||||
app.populateOnlineUsers = function () {
|
||||
var uids = [];
|
||||
|
||||
jQuery('.post-row').each(function () {
|
||||
@@ -276,9 +269,7 @@ var socket,
|
||||
socket.emit('api:user.get_online_users', uids);
|
||||
}
|
||||
|
||||
app.process_page = function () {
|
||||
app.populate_online_users();
|
||||
|
||||
function highlightNavigationLink() {
|
||||
var path = window.location.pathname,
|
||||
parts = path.split('/'),
|
||||
active = parts[parts.length - 1];
|
||||
@@ -295,10 +286,27 @@ var socket,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.createUserTooltips = function() {
|
||||
$('img[title].teaser-pic,img[title].user-img').each(function() {
|
||||
$(this).tooltip({
|
||||
placement: 'top',
|
||||
title: $(this).attr('title')
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.processPage = function () {
|
||||
app.populateOnlineUsers();
|
||||
|
||||
highlightNavigationLink();
|
||||
|
||||
$('span.timeago').timeago();
|
||||
$('.post-content img').addClass('img-responsive');
|
||||
|
||||
app.createUserTooltips();
|
||||
|
||||
setTimeout(function () {
|
||||
window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
|
||||
}, 100);
|
||||
@@ -331,6 +339,28 @@ var socket,
|
||||
}
|
||||
|
||||
app.openChat = function (username, touid) {
|
||||
if (username === app.username) {
|
||||
app.alert({
|
||||
type: 'warning',
|
||||
title: 'Invalid Chat',
|
||||
message: "You can't chat with yourself!",
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!app.username) {
|
||||
app.alert({
|
||||
type: 'danger',
|
||||
title: 'Not Logged In',
|
||||
message: 'Please log in to chat with <strong>' + username + '</strong>',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
require(['chat'], function (chat) {
|
||||
var chatModal;
|
||||
if (!chat.modalExists(touid)) {
|
||||
@@ -342,62 +372,7 @@ var socket,
|
||||
chat.center(chatModal);
|
||||
});
|
||||
}
|
||||
|
||||
app.createNewPosts = function (data) {
|
||||
if (data.posts[0].uid !== app.uid) {
|
||||
data.posts[0].display_moderator_tools = 'none';
|
||||
}
|
||||
|
||||
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
var uniqueid = new Date().getTime(),
|
||||
tempContainer = jQuery('<div id="' + uniqueid + '"></div>')
|
||||
.appendTo("#post-container")
|
||||
.hide()
|
||||
.append(translatedHTML)
|
||||
.fadeIn('slow');
|
||||
|
||||
for (var x = 0, numPosts = data.posts.length; x < numPosts; x++) {
|
||||
socket.emit('api:post.privileges', data.posts[x].pid);
|
||||
}
|
||||
|
||||
tempContainer.replaceWith(tempContainer.contents());
|
||||
infiniteLoaderActive = false;
|
||||
|
||||
app.populate_online_users();
|
||||
app.addCommasToNumbers();
|
||||
$('span.timeago').timeago();
|
||||
$('.post-content img').addClass('img-responsive');
|
||||
});
|
||||
}
|
||||
|
||||
app.infiniteLoaderActive = false;
|
||||
|
||||
app.loadMorePosts = function (tid, callback) {
|
||||
if (app.infiniteLoaderActive)
|
||||
return;
|
||||
app.infiniteLoaderActive = true;
|
||||
|
||||
if ($('#loading-indicator').attr('done') === '0')
|
||||
$('#loading-indicator').removeClass('hide');
|
||||
|
||||
socket.emit('api:topic.loadMore', {
|
||||
tid: tid,
|
||||
after: document.querySelectorAll('#post-container li[data-pid]').length
|
||||
}, function (data) {
|
||||
app.infiniteLoaderActive = false;
|
||||
if (data.posts.length) {
|
||||
$('#loading-indicator').attr('done', '0');
|
||||
app.createNewPosts(data);
|
||||
} else {
|
||||
$('#loading-indicator').attr('done', '1');
|
||||
}
|
||||
$('#loading-indicator').addClass('hide');
|
||||
if (callback)
|
||||
callback(data.posts);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
app.scrollToTop = function () {
|
||||
$('body,html').animate({
|
||||
scrollTop: 0
|
||||
@@ -410,43 +385,6 @@ var socket,
|
||||
});
|
||||
}
|
||||
|
||||
app.scrollToPost = function (pid) {
|
||||
|
||||
if (!pid)
|
||||
return;
|
||||
|
||||
var container = $(document.body),
|
||||
scrollTo = $('#post_anchor_' + pid),
|
||||
tid = $('#post-container').attr('data-tid');
|
||||
|
||||
function animateScroll() {
|
||||
$('body,html').animate({
|
||||
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
|
||||
}, 400);
|
||||
//$('body,html').scrollTop(scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height());
|
||||
}
|
||||
|
||||
if (!scrollTo.length && tid) {
|
||||
|
||||
var intervalID = setInterval(function () {
|
||||
app.loadMorePosts(tid, function (posts) {
|
||||
scrollTo = $('#post_anchor_' + pid);
|
||||
|
||||
if (tid && scrollTo.length) {
|
||||
animateScroll();
|
||||
}
|
||||
|
||||
if (!posts.length || scrollTo.length)
|
||||
clearInterval(intervalID);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
} else if (tid) {
|
||||
animateScroll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
jQuery('document').ready(function () {
|
||||
$('#search-form').on('submit', function () {
|
||||
var input = $(this).find('input');
|
||||
|
||||
@@ -10,9 +10,10 @@ define(['forum/accountheader'], function(header) {
|
||||
|
||||
$(document).ready(function() {
|
||||
var username = $('.account-username a').html();
|
||||
app.enter_room('user/' + theirid);
|
||||
app.enterRoom('user/' + theirid);
|
||||
|
||||
app.addCommasToNumbers();
|
||||
$('.user-recent-posts img').addClass('img-responsive');
|
||||
|
||||
var followBtn = $('#follow-btn');
|
||||
var unfollowBtn = $('#unfollow-btn');
|
||||
|
||||
@@ -129,7 +129,7 @@ define(function() {
|
||||
update_blockclass(ev.target);
|
||||
});
|
||||
|
||||
jQuery('.category_name, .category_description, .blockclass').on('change', function(ev) {
|
||||
jQuery('.category_name, .category_description, .blockclass .category_bgColor').on('change', function(ev) {
|
||||
modified(ev.target);
|
||||
});
|
||||
|
||||
@@ -156,6 +156,20 @@ define(function() {
|
||||
return false;
|
||||
});
|
||||
|
||||
// Colour Picker
|
||||
$('[data-name="bgColor"], [data-name="color"]').each(function(idx, inputEl) {
|
||||
var jinputEl = $(this),
|
||||
parentEl = jinputEl.parents('[data-cid]');
|
||||
jinputEl.ColorPicker({
|
||||
color: this.value || '#000',
|
||||
onChange: function(hsb, hex) {
|
||||
jinputEl.val('#' + hex);
|
||||
if (inputEl.getAttribute('data-name') === 'bgColor') parentEl.css('background', '#' + hex);
|
||||
else if (inputEl.getAttribute('data-name') === 'color') parentEl.css('color', '#' + hex);
|
||||
modified(inputEl);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -45,10 +45,12 @@ define(function() {
|
||||
|
||||
errorEl.html(errorText).removeClass('hide');
|
||||
} else {
|
||||
createModal.modal('hide');
|
||||
errorEl.addClass('hide');
|
||||
createNameEl.val('');
|
||||
ajaxify.go('admin/groups');
|
||||
createModal.on('hidden.bs.modal', function() {
|
||||
ajaxify.go('admin/groups');
|
||||
});
|
||||
createModal.modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ define(function() {
|
||||
document.getElementById('connections').innerHTML = total;
|
||||
});
|
||||
|
||||
app.enter_room('admin');
|
||||
app.enterRoom('admin');
|
||||
socket.emit('api:get_all_rooms');
|
||||
};
|
||||
|
||||
|
||||
@@ -42,8 +42,13 @@ define(function() {
|
||||
|
||||
loadMoreEl.addEventListener('click', function() {
|
||||
if (this.className.indexOf('disabled') === -1) {
|
||||
var topics = document.querySelectorAll('.topics li[data-tid]'),
|
||||
lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
|
||||
var topics = document.querySelectorAll('.topics li[data-tid]');
|
||||
|
||||
if(!topics.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
|
||||
|
||||
this.innerHTML = '<i class="icon-refresh icon-spin"></i> Retrieving topics';
|
||||
socket.emit('api:admin.topics.getMore', {
|
||||
|
||||
@@ -3,7 +3,6 @@ define(function () {
|
||||
|
||||
Category.init = function() {
|
||||
var cid = templates.get('category_id'),
|
||||
room = 'category_' + cid,
|
||||
twitterEl = jQuery('#twitter-intent'),
|
||||
facebookEl = jQuery('#facebook-share'),
|
||||
googleEl = jQuery('#google-share'),
|
||||
@@ -12,7 +11,7 @@ define(function () {
|
||||
google_url = templates.get('google-share-url'),
|
||||
loadingMoreTopics = false;
|
||||
|
||||
app.enter_room(room);
|
||||
app.enterRoom('category_' + cid);
|
||||
|
||||
twitterEl.on('click', function () {
|
||||
window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no');
|
||||
@@ -56,7 +55,7 @@ define(function () {
|
||||
li.setAttribute('data-pid', posts[i].pid);
|
||||
|
||||
|
||||
li.innerHTML = '<a href="/user/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-rounded" src="' + posts[i].picture + '" class="" /></a>' +
|
||||
li.innerHTML = '<a href="/user/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-rounded user-img" src="' + posts[i].picture + '" class="" /></a>' +
|
||||
'<a href="/topic/' + posts[i].topicSlug + '#' + posts[i].pid + '">' +
|
||||
'<strong><span>'+ posts[i].username + '</span></strong>' +
|
||||
'<p>' +
|
||||
@@ -69,6 +68,7 @@ define(function () {
|
||||
recent_replies.appendChild(frag);
|
||||
}
|
||||
$('#category_recent_replies span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
});
|
||||
|
||||
$(window).off('scroll').on('scroll', function (ev) {
|
||||
@@ -94,17 +94,20 @@ define(function () {
|
||||
|
||||
if (numTopics > 0) {
|
||||
for (var x = 0; x < numTopics; x++) {
|
||||
if ($(topics[x]).find('.icon-pushpin').length)
|
||||
if ($(topics[x]).find('.icon-pushpin').length) {
|
||||
if(x === numTopics - 1) {
|
||||
topic.insertAfter(topics[x]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
topic.insertBefore(topics[x]);
|
||||
topic.hide().fadeIn('slow');
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
container.append(topic);
|
||||
topic.hide().fadeIn('slow');
|
||||
}
|
||||
|
||||
topic.hide().fadeIn('slow');
|
||||
socket.emit('api:categories.getRecentReplies', templates.get('category_id'));
|
||||
|
||||
addActiveUser(data);
|
||||
|
||||
@@ -36,7 +36,9 @@ define(function() {
|
||||
} else {
|
||||
$('#login-error-notify').hide();
|
||||
|
||||
if(!app.previousUrl) { app.previousUrl = '/'; }
|
||||
if(!app.previousUrl) {
|
||||
app.previousUrl = '/';
|
||||
}
|
||||
|
||||
if(app.previousUrl.indexOf('/reset/') != -1)
|
||||
window.location.replace(RELATIVE_PATH + "/?loggedin");
|
||||
@@ -63,6 +65,12 @@ define(function() {
|
||||
});
|
||||
|
||||
document.querySelector('#content input').focus();
|
||||
|
||||
if(!config.emailSetup)
|
||||
$('#reset-link').addClass('hide');
|
||||
else
|
||||
$('#reset-link').removeClass('hide');
|
||||
|
||||
};
|
||||
|
||||
return Login;
|
||||
|
||||
@@ -8,7 +8,7 @@ define(function() {
|
||||
var active = '';
|
||||
|
||||
Recent.init = function() {
|
||||
app.enter_room('recent_posts');
|
||||
app.enterRoom('recent_posts');
|
||||
|
||||
ajaxify.register_events([
|
||||
'event:new_topic',
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
define(function() {
|
||||
var Topic = {};
|
||||
var Topic = {},
|
||||
infiniteLoaderActive = false;
|
||||
|
||||
|
||||
Topic.init = function() {
|
||||
var expose_tools = templates.get('expose_tools'),
|
||||
@@ -17,16 +19,26 @@ define(function() {
|
||||
google_url = templates.get('google-share-url');
|
||||
|
||||
|
||||
function fixDeleteStateForPosts() {
|
||||
var postEls = document.querySelectorAll('#post-container li[data-deleted]');
|
||||
for (var x = 0, numPosts = postEls.length; x < numPosts; x++) {
|
||||
if (postEls[x].getAttribute('data-deleted') === '1') {
|
||||
toggle_post_delete_state(postEls[x].getAttribute('data-pid'));
|
||||
}
|
||||
postEls[x].removeAttribute('data-deleted');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
jQuery('document').ready(function() {
|
||||
|
||||
app.addCommasToNumbers();
|
||||
|
||||
app.enterRoom('topic_' + tid);
|
||||
|
||||
var room = 'topic_' + tid,
|
||||
adminTools = document.getElementById('thread-tools');
|
||||
|
||||
app.enter_room(room);
|
||||
|
||||
if($('#post-container .sub-posts').length) {
|
||||
$('.topic-main-buttons').removeClass('hide').parent().removeClass('hide');
|
||||
}
|
||||
|
||||
$('.twitter-share').on('click', function () {
|
||||
window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no');
|
||||
@@ -50,11 +62,10 @@ define(function() {
|
||||
|
||||
if (expose_tools === '1') {
|
||||
var moveThreadModal = $('#move_thread_modal');
|
||||
|
||||
adminTools.style.visibility = 'inherit';
|
||||
$('.thread-tools').removeClass('hide');
|
||||
|
||||
// Add events to the thread tools
|
||||
$('#delete_thread').on('click', function(e) {
|
||||
$('.delete_thread').on('click', function(e) {
|
||||
if (thread_state.deleted !== '1') {
|
||||
bootbox.confirm('Are you sure you want to delete this thread?', function(confirm) {
|
||||
if (confirm) {
|
||||
@@ -73,7 +84,7 @@ define(function() {
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#lock_thread').on('click', function(e) {
|
||||
$('.lock_thread').on('click', function(e) {
|
||||
if (thread_state.locked !== '1') {
|
||||
socket.emit('api:topic.lock', {
|
||||
tid: tid
|
||||
@@ -86,7 +97,7 @@ define(function() {
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#pin_thread').on('click', function(e) {
|
||||
$('.pin_thread').on('click', function(e) {
|
||||
if (thread_state.pinned !== '1') {
|
||||
socket.emit('api:topic.pin', {
|
||||
tid: tid
|
||||
@@ -99,7 +110,7 @@ define(function() {
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#move_thread').on('click', function(e) {
|
||||
$('.move_thread').on('click', function(e) {
|
||||
moveThreadModal.modal('show');
|
||||
return false;
|
||||
});
|
||||
@@ -109,7 +120,6 @@ define(function() {
|
||||
var loadingEl = document.getElementById('categories-loading');
|
||||
if (loadingEl) {
|
||||
socket.once('api:categories.get', function(data) {
|
||||
console.log(data);
|
||||
// Render categories
|
||||
var categoriesFrag = document.createDocumentFragment(),
|
||||
categoryEl = document.createElement('li'),
|
||||
@@ -182,12 +192,8 @@ define(function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Fix delete state for this thread's posts
|
||||
var postEls = document.querySelectorAll('#post-container li[data-deleted]');
|
||||
for (var x = 0, numPosts = postEls.length; x < numPosts; x++) {
|
||||
if (postEls[x].getAttribute('data-deleted') === '1') toggle_post_delete_state(postEls[x].getAttribute('data-pid'));
|
||||
postEls[x].removeAttribute('data-deleted');
|
||||
}
|
||||
fixDeleteStateForPosts();
|
||||
|
||||
|
||||
// Follow Thread State
|
||||
var followEl = $('.main-post .follow'),
|
||||
@@ -246,7 +252,7 @@ define(function() {
|
||||
var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
|
||||
|
||||
if(bookmark) {
|
||||
app.scrollToPost(parseInt(bookmark, 10));
|
||||
Topic.scrollToPost(parseInt(bookmark, 10));
|
||||
}
|
||||
|
||||
$('#post-container').on('click', '.deleted', function(ev) {
|
||||
@@ -258,13 +264,15 @@ define(function() {
|
||||
$(window).off('scroll').on('scroll', function() {
|
||||
var bottom = ($(document).height() - $(window).height()) * 0.9;
|
||||
|
||||
if ($(window).scrollTop() > bottom && !app.infiniteLoaderActive && $('#post-container').children().length) {
|
||||
app.loadMorePosts(tid);
|
||||
if ($(window).scrollTop() > bottom && !infiniteLoaderActive && $('#post-container').children().length) {
|
||||
loadMorePosts(tid, function(posts) {
|
||||
fixDeleteStateForPosts();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var reply_fn = function() {
|
||||
$('.topic').on('click', '.post_reply', function() {
|
||||
var selectionText = '',
|
||||
selection = window.getSelection() || document.getSelection();
|
||||
|
||||
@@ -278,9 +286,7 @@ define(function() {
|
||||
cmp.push(tid, null, null, selectionText.length > 0 ? selectionText + '\n\n' : '');
|
||||
});
|
||||
}
|
||||
};
|
||||
$('#post-container').on('click', '.post_reply', reply_fn);
|
||||
$('#post_reply').on('click', reply_fn);
|
||||
});
|
||||
|
||||
$('#post-container').on('click', '.quote', function() {
|
||||
if (thread_state.locked !== '1') {
|
||||
@@ -307,12 +313,12 @@ define(function() {
|
||||
if (element.attr('class') == 'icon-star-empty') {
|
||||
socket.emit('api:posts.favourite', {
|
||||
pid: pid,
|
||||
room_id: app.current_room
|
||||
room_id: app.currentRoom
|
||||
});
|
||||
} else {
|
||||
socket.emit('api:posts.unfavourite', {
|
||||
pid: pid,
|
||||
room_id: app.current_room
|
||||
room_id: app.currentRoom
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -341,13 +347,25 @@ define(function() {
|
||||
confirmDel = confirm((deleteAction ? 'Delete' : 'Restore') + ' this post?');
|
||||
|
||||
if (confirmDel) {
|
||||
deleteAction ?
|
||||
if(deleteAction) {
|
||||
socket.emit('api:posts.delete', {
|
||||
pid: pid
|
||||
}) :
|
||||
socket.emit('api:posts.restore', {
|
||||
pid: pid
|
||||
pid: pid,
|
||||
tid: tid
|
||||
}, function(err) {
|
||||
if(err) {
|
||||
return app.alertError('Can\'t delete post!');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
socket.emit('api:posts.restore', {
|
||||
pid: pid,
|
||||
tid: tid
|
||||
}, function(err) {
|
||||
if(err) {
|
||||
return app.alertError('Can\'t restore post!');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -355,9 +373,6 @@ define(function() {
|
||||
var username = $(this).parents('li.row').attr('data-username');
|
||||
var touid = $(this).parents('li.row').attr('data-uid');
|
||||
|
||||
if (username === app.username || !app.username)
|
||||
return;
|
||||
|
||||
app.openChat(username, touid);
|
||||
});
|
||||
|
||||
@@ -381,12 +396,15 @@ define(function() {
|
||||
var userLink = $('<a href="/user/' + userslug + '"></a>').append(userIcon);
|
||||
userLink.attr('data-uid', uid);
|
||||
|
||||
var div = $('<div class="inline-block"></div>');
|
||||
div.append(userLink);
|
||||
|
||||
userLink.tooltip({
|
||||
placement: 'left',
|
||||
placement: 'top',
|
||||
title: username
|
||||
});
|
||||
|
||||
return userLink;
|
||||
return div;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +446,7 @@ define(function() {
|
||||
activeEl.find('.anonymous-box').remove();
|
||||
if(anonymousCount || remainingUsers) {
|
||||
|
||||
var anonLink = $('<i class="icon-user anonymous-box"></i>');
|
||||
var anonLink = $('<div class="anonymous-box inline-block"><i class="icon-user"></i></div>');
|
||||
activeEl.append(anonLink);
|
||||
|
||||
var title = '';
|
||||
@@ -440,12 +458,12 @@ define(function() {
|
||||
title = anonymousCount + ' guest(s)';
|
||||
|
||||
anonLink.tooltip({
|
||||
placement: 'left',
|
||||
placement: 'top',
|
||||
title: title
|
||||
});
|
||||
}
|
||||
}
|
||||
app.populate_online_users();
|
||||
app.populateOnlineUsers();
|
||||
});
|
||||
|
||||
socket.on('event:rep_up', function(data) {
|
||||
@@ -456,7 +474,7 @@ define(function() {
|
||||
adjust_rep(-1, data.pid, data.uid);
|
||||
});
|
||||
|
||||
socket.on('event:new_post', app.createNewPosts);
|
||||
socket.on('event:new_post', createNewPosts);
|
||||
|
||||
socket.on('event:topic_deleted', function(data) {
|
||||
if (data.tid === tid && data.status === 'ok') {
|
||||
@@ -540,11 +558,15 @@ define(function() {
|
||||
});
|
||||
|
||||
socket.on('event:post_deleted', function(data) {
|
||||
if (data.pid) toggle_post_delete_state(data.pid, true);
|
||||
if (data.pid) {
|
||||
toggle_post_delete_state(data.pid);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('event:post_restored', function(data) {
|
||||
if (data.pid) toggle_post_delete_state(data.pid, true);
|
||||
if (data.pid) {
|
||||
toggle_post_delete_state(data.pid);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('api:post.privileges', function(privileges) {
|
||||
@@ -566,19 +588,19 @@ define(function() {
|
||||
}
|
||||
|
||||
function set_locked_state(locked, alert) {
|
||||
var threadReplyBtn = document.getElementById('post_reply'),
|
||||
var threadReplyBtn = $('.topic-main-buttons .post_reply'),
|
||||
postReplyBtns = document.querySelectorAll('#post-container .post_reply'),
|
||||
quoteBtns = document.querySelectorAll('#post-container .quote'),
|
||||
editBtns = document.querySelectorAll('#post-container .edit'),
|
||||
deleteBtns = document.querySelectorAll('#post-container .delete'),
|
||||
numPosts = document.querySelectorAll('#post_container li[data-pid]').length,
|
||||
lockThreadEl = document.getElementById('lock_thread'),
|
||||
lockThreadEl = $('.lock_thread'),
|
||||
x;
|
||||
|
||||
if (locked === true) {
|
||||
lockThreadEl.innerHTML = '<i class="icon-unlock"></i> Unlock Thread';
|
||||
threadReplyBtn.disabled = true;
|
||||
threadReplyBtn.innerHTML = 'Locked <i class="icon-lock"></i>';
|
||||
lockThreadEl.html('<i class="icon-unlock"></i> Unlock Thread');
|
||||
threadReplyBtn.attr('disabled', true);
|
||||
threadReplyBtn.html('Locked <i class="icon-lock"></i>');
|
||||
for (x = 0; x < numPosts; x++) {
|
||||
postReplyBtns[x].innerHTML = 'Locked <i class="icon-lock"></i>';
|
||||
quoteBtns[x].style.display = 'none';
|
||||
@@ -598,9 +620,9 @@ define(function() {
|
||||
|
||||
thread_state.locked = '1';
|
||||
} else {
|
||||
lockThreadEl.innerHTML = '<i class="icon-lock"></i> Lock Thread';
|
||||
threadReplyBtn.disabled = false;
|
||||
threadReplyBtn.innerHTML = 'Reply';
|
||||
lockThreadEl.html('<i class="icon-lock"></i> Lock Thread');
|
||||
threadReplyBtn.attr('disabled', false);
|
||||
threadReplyBtn.html('Reply');
|
||||
for (x = 0; x < numPosts; x++) {
|
||||
postReplyBtns[x].innerHTML = 'Reply <i class="icon-reply"></i>';
|
||||
quoteBtns[x].style.display = 'inline-block';
|
||||
@@ -623,13 +645,14 @@ define(function() {
|
||||
}
|
||||
|
||||
function set_delete_state(deleted) {
|
||||
var deleteThreadEl = document.getElementById('delete_thread'),
|
||||
deleteTextEl = deleteThreadEl.getElementsByTagName('span')[0],
|
||||
var deleteThreadEl = $('.delete_thread'),
|
||||
deleteTextEl = $('.delete_thread span'),
|
||||
//deleteThreadEl.getElementsByTagName('span')[0],
|
||||
threadEl = $('#post-container'),
|
||||
deleteNotice = document.getElementById('thread-deleted') || document.createElement('div');
|
||||
|
||||
if (deleted) {
|
||||
deleteTextEl.innerHTML = '<i class="icon-comment"></i> Restore Thread';
|
||||
deleteTextEl.html('<i class="icon-comment"></i> Restore Thread');
|
||||
threadEl.addClass('deleted');
|
||||
|
||||
// Spawn a 'deleted' notice at the top of the page
|
||||
@@ -640,7 +663,7 @@ define(function() {
|
||||
|
||||
thread_state.deleted = '1';
|
||||
} else {
|
||||
deleteTextEl.innerHTML = '<i class="icon-trash"></i> Delete Thread';
|
||||
deleteTextEl.html('<i class="icon-trash"></i> Delete Thread');
|
||||
threadEl.removeClass('deleted');
|
||||
deleteNotice.parentNode.removeChild(deleteNotice);
|
||||
|
||||
@@ -649,10 +672,10 @@ define(function() {
|
||||
}
|
||||
|
||||
function set_pinned_state(pinned, alert) {
|
||||
var pinEl = document.getElementById('pin_thread');
|
||||
var pinEl = $('.pin_thread');
|
||||
|
||||
if (pinned) {
|
||||
pinEl.innerHTML = '<i class="icon-pushpin"></i> Unpin Thread';
|
||||
pinEl.html('<i class="icon-pushpin"></i> Unpin Thread');
|
||||
if (alert) {
|
||||
app.alert({
|
||||
'alert_id': 'thread_pin',
|
||||
@@ -665,7 +688,7 @@ define(function() {
|
||||
|
||||
thread_state.pinned = '1';
|
||||
} else {
|
||||
pinEl.innerHTML = '<i class="icon-pushpin"></i> Pin Thread';
|
||||
pinEl.html('<i class="icon-pushpin"></i> Pin Thread');
|
||||
if (alert) {
|
||||
app.alert({
|
||||
'alert_id': 'thread_pin',
|
||||
@@ -702,6 +725,7 @@ define(function() {
|
||||
} else {
|
||||
postEl.toggleClass('none');
|
||||
}
|
||||
updatePostCount();
|
||||
});
|
||||
socket.emit('api:post.privileges', pid);
|
||||
}
|
||||
@@ -811,5 +835,138 @@ define(function() {
|
||||
window.onload = updateHeader;
|
||||
};
|
||||
|
||||
Topic.scrollToPost = function(pid) {
|
||||
if (!pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var container = $(document.body),
|
||||
scrollTo = $('#post_anchor_' + pid),
|
||||
tid = $('#post-container').attr('data-tid');
|
||||
|
||||
function animateScroll() {
|
||||
$('body,html').animate({
|
||||
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
|
||||
}, 400);
|
||||
}
|
||||
|
||||
if (!scrollTo.length && tid) {
|
||||
|
||||
var intervalID = setInterval(function () {
|
||||
loadMorePosts(tid, function (posts) {
|
||||
scrollTo = $('#post_anchor_' + pid);
|
||||
|
||||
if (tid && scrollTo.length) {
|
||||
animateScroll();
|
||||
}
|
||||
|
||||
if (!posts.length || scrollTo.length)
|
||||
clearInterval(intervalID);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
} else if (tid) {
|
||||
animateScroll();
|
||||
}
|
||||
}
|
||||
|
||||
function createNewPosts(data, infiniteLoaded) {
|
||||
if(!data || (data.posts && !data.posts.length))
|
||||
return;
|
||||
|
||||
if (data.posts[0].uid !== app.uid) {
|
||||
data.posts[0].display_moderator_tools = 'none';
|
||||
}
|
||||
|
||||
function removeAlreadyAddedPosts() {
|
||||
data.posts = data.posts.filter(function(post) {
|
||||
return $('#post-container li[data-pid="' + post.pid +'"]').length === 0;
|
||||
});
|
||||
}
|
||||
|
||||
function findInsertionPoint() {
|
||||
var after = null,
|
||||
firstPid = data.posts[0].pid;
|
||||
$('#post-container li[data-pid]').each(function() {
|
||||
if(parseInt(firstPid, 10) > parseInt($(this).attr('data-pid'), 10)) {
|
||||
after = $(this);
|
||||
if(after.hasClass('main-post')) {
|
||||
after = after.next();
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return after;
|
||||
}
|
||||
|
||||
removeAlreadyAddedPosts();
|
||||
if(!data.posts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var insertAfter = findInsertionPoint();
|
||||
|
||||
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
|
||||
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
var translated = $(translatedHTML);
|
||||
if(!infiniteLoaded) {
|
||||
translated.removeClass('infiniteloaded');
|
||||
}
|
||||
|
||||
translated.insertAfter(insertAfter)
|
||||
.hide()
|
||||
.fadeIn('slow');
|
||||
|
||||
for (var x = 0, numPosts = data.posts.length; x < numPosts; x++) {
|
||||
socket.emit('api:post.privileges', data.posts[x].pid);
|
||||
}
|
||||
|
||||
infiniteLoaderActive = false;
|
||||
|
||||
app.populateOnlineUsers();
|
||||
app.addCommasToNumbers();
|
||||
$('span.timeago').timeago();
|
||||
$('.post-content img').addClass('img-responsive');
|
||||
updatePostCount();
|
||||
});
|
||||
}
|
||||
|
||||
function updatePostCount() {
|
||||
$('#topic-post-count').html($('#post-container li[data-pid]:not(.deleted)').length);
|
||||
}
|
||||
|
||||
function loadMorePosts(tid, callback) {
|
||||
var indicatorEl = $('.loading-indicator');
|
||||
|
||||
if (infiniteLoaderActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
infiniteLoaderActive = true;
|
||||
|
||||
if (indicatorEl.attr('done') === '0') {
|
||||
indicatorEl.fadeIn();
|
||||
}
|
||||
|
||||
socket.emit('api:topic.loadMore', {
|
||||
tid: tid,
|
||||
after: $('#post-container .post-row.infiniteloaded').length
|
||||
}, function (data) {
|
||||
infiniteLoaderActive = false;
|
||||
if (data.posts.length) {
|
||||
indicatorEl.attr('done', '0');
|
||||
createNewPosts(data, true);
|
||||
} else {
|
||||
indicatorEl.attr('done', '1');
|
||||
}
|
||||
indicatorEl.fadeOut();
|
||||
if (callback) {
|
||||
callback(data.posts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Topic;
|
||||
});
|
||||
@@ -210,13 +210,17 @@ define(['taskbar'], function(taskbar) {
|
||||
selectionEnd = postContentEl.selectionEnd,
|
||||
selectionLength = selectionEnd - selectionStart;
|
||||
|
||||
function insertIntoInput(element, value) {
|
||||
var start = postContentEl.selectionStart;
|
||||
element.value = element.value.slice(0, start) + value + element.value.slice(start, element.value.length);
|
||||
postContentEl.selectionStart = postContentEl.selectionEnd = start + value.length;
|
||||
}
|
||||
|
||||
switch(iconClass) {
|
||||
case 'icon-bold':
|
||||
if (selectionStart === selectionEnd) {
|
||||
// Nothing selected
|
||||
postContentEl.value = postContentEl.value + '**bolded text**';
|
||||
postContentEl.selectionStart = cursorEnd+2;
|
||||
postContentEl.selectionEnd = postContentEl.value.length - 2;
|
||||
insertIntoInput(postContentEl, "**bolded text**");
|
||||
} else {
|
||||
// Text selected
|
||||
postContentEl.value = postContentEl.value.slice(0, selectionStart) + '**' + postContentEl.value.slice(selectionStart, selectionEnd) + '**' + postContentEl.value.slice(selectionEnd);
|
||||
@@ -227,9 +231,7 @@ define(['taskbar'], function(taskbar) {
|
||||
case 'icon-italic':
|
||||
if (selectionStart === selectionEnd) {
|
||||
// Nothing selected
|
||||
postContentEl.value = postContentEl.value + '*italicised text*';
|
||||
postContentEl.selectionStart = cursorEnd+1;
|
||||
postContentEl.selectionEnd = postContentEl.value.length - 1;
|
||||
insertIntoInput(postContentEl, "*italicised text*");
|
||||
} else {
|
||||
// Text selected
|
||||
postContentEl.value = postContentEl.value.slice(0, selectionStart) + '*' + postContentEl.value.slice(selectionStart, selectionEnd) + '*' + postContentEl.value.slice(selectionEnd);
|
||||
@@ -239,16 +241,12 @@ define(['taskbar'], function(taskbar) {
|
||||
break;
|
||||
case 'icon-list':
|
||||
// Nothing selected
|
||||
postContentEl.value = postContentEl.value + "\n\n* list item";
|
||||
postContentEl.selectionStart = cursorEnd+4;
|
||||
postContentEl.selectionEnd = postContentEl.value.length;
|
||||
insertIntoInput(postContentEl, "\n\n* list item");
|
||||
break;
|
||||
case 'icon-link':
|
||||
if (selectionStart === selectionEnd) {
|
||||
// Nothing selected
|
||||
postContentEl.value = postContentEl.value + '[link text](link url)';
|
||||
postContentEl.selectionStart = cursorEnd+12;
|
||||
postContentEl.selectionEnd = postContentEl.value.length - 1;
|
||||
insertIntoInput(postContentEl, "[link text](link url)");
|
||||
} else {
|
||||
// Text selected
|
||||
postContentEl.value = postContentEl.value.slice(0, selectionStart) + '[' + postContentEl.value.slice(selectionStart, selectionEnd) + '](link url)' + postContentEl.value.slice(selectionEnd);
|
||||
@@ -258,6 +256,7 @@ define(['taskbar'], function(taskbar) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
if (composer.active !== undefined) composer.reposition(composer.active);
|
||||
});
|
||||
|
||||
@@ -235,11 +235,11 @@
|
||||
}
|
||||
|
||||
function makeRegex(block) {
|
||||
return new RegExp("<!-- BEGIN " + block + " -->[\\s\\S]*<!-- END " + block + " -->", 'g');
|
||||
return new RegExp("<!--[\\s]*BEGIN " + block + "[\\s]*-->[\\s\\S]*<!--[\\s]*END " + block + "[\\s]*-->", 'g');
|
||||
}
|
||||
|
||||
function makeConditionalRegex(block) {
|
||||
return new RegExp("<!-- IF " + block + " -->[\\s\\S]*<!-- ENDIF " + block + " -->", 'g');
|
||||
return new RegExp("<!--[\\s]*IF " + block + "[\\s]*-->[\\s\\S]*<!--[\\s]*ENDIF " + block + "[\\s]*-->", 'g');
|
||||
}
|
||||
|
||||
function getBlock(regex, block, template) {
|
||||
@@ -249,21 +249,8 @@
|
||||
if (self.blocks && block !== undefined) self.blocks[block] = data[0];
|
||||
|
||||
data = data[0]
|
||||
.replace("<!-- BEGIN " + block + " -->", "")
|
||||
.replace("<!-- END " + block + " -->", "");
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function getConditionalBlock(regex, block, template) {
|
||||
data = template.match(regex);
|
||||
if (data == null) return;
|
||||
|
||||
if (self.blocks && block !== undefined) self.blocks[block] = data[0];
|
||||
|
||||
data = data[0]
|
||||
.replace("<!-- IF " + block + " -->", "")
|
||||
.replace("<!-- ENDIF " + block + " -->", "");
|
||||
.replace("<!--[\\s]*BEGIN " + block + "[\\s]*-->", "")
|
||||
.replace("<!--[\\s]*END " + block + "[\\s]*-->", "");
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -317,14 +304,29 @@
|
||||
block = parse(data[d], namespace, block);
|
||||
template = setBlock(regex, block, template);
|
||||
} else {
|
||||
var conditional = makeConditionalRegex(d),
|
||||
block = getConditionalBlock(conditional, namespace, template);
|
||||
var conditional = makeConditionalRegex(namespace + d);
|
||||
|
||||
if (block && !data[d]) {
|
||||
template = template.replace(conditional, '');
|
||||
var conditionalBlock = conditional.exec(template);
|
||||
|
||||
if (conditionalBlock !== null) {
|
||||
conditionalBlock = conditionalBlock[0].split(/<!-- ELSE -->/);
|
||||
|
||||
if (conditionalBlock[1]) {
|
||||
// there is an else statement
|
||||
if (!data[d]) {
|
||||
template = template.replace(conditional, conditionalBlock[1]);
|
||||
} else {
|
||||
template = template.replace(conditional, conditionalBlock[0]);
|
||||
}
|
||||
|
||||
} else {
|
||||
// regular if
|
||||
if (!data[d]) {
|
||||
template = template.replace(conditional, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template = replace(namespace + d, data[d], template);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<br/>
|
||||
|
||||
<span class="account-bio-label">website</span>
|
||||
<span><a href="{website}">{website}</a></span>
|
||||
<span><a href="{website}">{websiteName}</a></span>
|
||||
<br/>
|
||||
|
||||
<span class="account-bio-label">location</span>
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
<!-- BEGIN groups -->
|
||||
<li data-gid="{groups.gid}">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-8">
|
||||
<h2>{groups.name}</h2>
|
||||
<p>{groups.description}</p>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default" data-action="members">Members</button>
|
||||
<!-- IF groups.deletable -->
|
||||
<button class="btn btn-danger" data-action="delete">Delete Group</button>
|
||||
<!-- ENDIF groups.deletable -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<ul class="pull-right members">
|
||||
<!-- BEGIN members -->
|
||||
<li data-uid="{groups.members.uid}" title="{groups.members.username}"><img src="{groups.members.picture}" /></li>
|
||||
<!-- END members -->
|
||||
<!-- BEGIN members --><li data-uid="{groups.members.uid}" title="{groups.members.username}"><img src="{groups.members.picture}" /></li><!-- END members -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<script type="text/javascript" src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="http://code.jquery.com/qunit/qunit-git.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/qunit/qunit-git.css">
|
||||
<link rel="stylesheet" type="text/css" href="{relative_path}/vendor/colorpicker/colorpicker.css">
|
||||
<script type="text/javascript" src="{relative_path}/socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="{relative_path}/src/app.js"></script>
|
||||
<script type="text/javascript" src="{relative_path}/src/templates.js"></script>
|
||||
@@ -21,6 +22,7 @@
|
||||
<script src="{relative_path}/vendor/jquery/js/jquery.form.js"></script>
|
||||
<script src="{relative_path}/vendor/requirejs/require.js"></script>
|
||||
<script src="{relative_path}/vendor/bootbox/bootbox.min.js"></script>
|
||||
<script src="{relative_path}/vendor/colorpicker/colorpicker.js"></script>
|
||||
|
||||
<script>
|
||||
require.config({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<h1>Topics</h1>
|
||||
<hr />
|
||||
|
||||
|
||||
<ul class="topics">
|
||||
<!-- BEGIN topics -->
|
||||
<li data-tid="{topics.tid}" data-locked="{topics.locked}" data-pinned="{topics.pinned}" data-deleted="{topics.deleted}">
|
||||
@@ -19,6 +20,14 @@
|
||||
<!-- END topics -->
|
||||
</ul>
|
||||
|
||||
<!-- IF notopics -->
|
||||
<div class="alert alert-warning" id="category-no-topics">
|
||||
<strong>There are no topics.</strong>
|
||||
</div>
|
||||
<!-- ELSE -->
|
||||
<div class="text-center">
|
||||
<button id="topics_loadmore" class="btn btn-primary btn-lg">Load More Topics</button>
|
||||
</div>
|
||||
<!-- ENDIF notopics -->
|
||||
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@
|
||||
</span>
|
||||
|
||||
<span class="pull-right hidden-xs">
|
||||
<!-- IF topics.unreplied -->
|
||||
No one has replied
|
||||
<!-- ELSE -->
|
||||
<a href="/user/{topics.teaser_userslug}">
|
||||
<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
|
||||
</a>
|
||||
@@ -69,7 +72,7 @@
|
||||
replied
|
||||
</a>
|
||||
<span class="timeago" title="{topics.teaser_timestamp}"></span>
|
||||
|
||||
<!-- ENDIF topics.unreplied -->
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
@@ -95,7 +98,7 @@
|
||||
</div>
|
||||
<div class="block-content active-users">
|
||||
<!-- BEGIN active_users -->
|
||||
<a data-uid="{active_users.uid}" href="/user/{active_users.userslug}"><img title="{active_users.username}" src="{active_users.picture}" class="img-rounded" /></a>
|
||||
<a data-uid="{active_users.uid}" href="/user/{active_users.userslug}"><img title="{active_users.username}" src="{active_users.picture}" class="img-rounded user-img" /></a>
|
||||
<!-- END active_users -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
<!-- BEGIN posts -->
|
||||
<div class="topic-row img-thumbnail clearfix" topic-url="topic/{posts.tid}/#{posts.pid}">
|
||||
<span><strong>{posts.username}</strong> : </span>
|
||||
<span>{posts.content}</span>
|
||||
<span>{posts.category_name} >> {posts.title}</span>
|
||||
<div>{posts.content}</div>
|
||||
<div>
|
||||
<span class="pull-right timeago" title="{posts.relativeTime}"></span>
|
||||
</div>
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
|
||||
|
||||
<footer id="footer" class="container footer">
|
||||
{footerHTML}
|
||||
<div class="copyright">Copyright © 2013 <a target="_blank" href="http://www.nodebb.org">NodeBB</a> by <a target="_blank" href="https://github.com/psychobunny">psychobunny</a>, <a href="https://github.com/julianlam" target="_blank">julianlam</a>, <a href="https://github.com/barisusakli" target="_blank">barisusakli</a> from <a target="_blank" href="http://www.designcreateplay.com">designcreateplay</a></div>
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -116,7 +116,13 @@
|
||||
</ul>
|
||||
|
||||
<ul id="logged-out-menu" class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<li class="visible-lg visible-md visible-sm">
|
||||
<a href="/register">Register</a>
|
||||
</li>
|
||||
<li class="visible-lg visible-md visible-sm">
|
||||
<a href="/login">Login</a>
|
||||
</li>
|
||||
<li class="visible-xs">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="loggedout_dropdown"><i class="icon-signin"></i></a>
|
||||
<ul class="dropdown-menu" aria-labelledby="loggedout_dropdown">
|
||||
<li>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<a href="category/{categories.slug}" itemprop="url">
|
||||
<meta itemprop="name" content="{categories.name}">
|
||||
<h4><span class="badge {categories.badgeclass}">{categories.topic_count} </span> {categories.name}</h4>
|
||||
<div class="icon {categories.blockclass}">
|
||||
<div class="icon" style="background: {categories.bgColor}; color: {categories.color};">
|
||||
<div id="category-{categories.cid}" class="category-slider-{categories.post_count}">
|
||||
<div class="category-box"><i class="{categories.icon} icon-4x"></i></div>
|
||||
<div class="category-box" itemprop="description">{categories.description}</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
<h1>User Privilege Thresholds</h1>
|
||||
|
||||
<form class="form-inline">
|
||||
@@ -15,13 +14,13 @@
|
||||
<label>Manage Content</label> <input type="number" class="input-mini" value="1000" placeholder="1000" data-field="privileges:manage_content" />
|
||||
</p>
|
||||
<p>
|
||||
Users with reach the "Manage Content" threshold are able to edit/delete other users' posts.
|
||||
Users who reach the "Manage Content" threshold are able to edit/delete other users' posts.
|
||||
</p>
|
||||
<p>
|
||||
<label>Manage Topics</label> <input type="number" class="input-mini" value="2000" placeholder="2000" data-field="privileges:manage_topic" />
|
||||
</p>
|
||||
<p>
|
||||
Users with reach the "Manage Topics" threshold are able to edit, lock, pin, close, and delete topics.
|
||||
Users who reach the "Manage Topics" threshold are able to edit, lock, pin, close, and delete topics.
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -43,4 +42,4 @@
|
||||
document.location.href = '/';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-offset-2 col-lg-10">
|
||||
<button class="btn btn-primary" id="login" type="submit">[[login:login]]</button> <a href="/reset">[[login:forgot_password]]</a>
|
||||
<button class="btn btn-primary" id="login" type="submit">[[login:login]]</button> <a id="reset-link" class="hide" href="/reset">[[login:forgot_password]]</a>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_csrf" value="{token}" id="csrf-token" />
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ul class="notifications-list">
|
||||
<!-- BEGIN notifications -->
|
||||
<li data-nid="{notifications.nid}" class="{notifications.readClass}">
|
||||
<a href="..{notifications.path}">{notifications.text}</a>
|
||||
<a href="{notifications.path}">{notifications.text}</a>
|
||||
<p class="timestamp">
|
||||
<span class="timeago" title="{notifications.datetimeISO}"></span>
|
||||
</p>
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
|
||||
<div class="well">
|
||||
<h3>
|
||||
You are now leaving NodeBB.
|
||||
You are now leaving {title}.
|
||||
</h3>
|
||||
<p>
|
||||
<a href="{url}" rel="nofollow" class="btn btn-primary btn-lg">Continue to {url}</a>
|
||||
<a id="return-btn" href="#" class="btn btn-lg btn-warning">Return to NodeBB</a>
|
||||
<a id="return-btn" href="#" class="btn btn-lg btn-warning">Return to {title}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li class="active">{category_name}</li>
|
||||
<div id="category_active_users"></div>
|
||||
<li class="active">Recent <a href="./recent.rss"><i class="icon-rss-sign"></i></a></li>
|
||||
</ol>
|
||||
|
||||
<ul class="nav nav-pills">
|
||||
@@ -21,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
<div class="category row">
|
||||
<div class="{topic_row_size}">
|
||||
<div class="col-md-12">
|
||||
<ul id="topics-container">
|
||||
<!-- BEGIN topics -->
|
||||
<li class="category-item {topics.deleted-class}">
|
||||
@@ -55,6 +54,9 @@
|
||||
</span>
|
||||
|
||||
<span class="pull-right hidden-xs">
|
||||
<!-- IF topics.unreplied -->
|
||||
No one has replied
|
||||
<!-- ELSE -->
|
||||
<a href="/user/{topics.teaser_userslug}">
|
||||
<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
|
||||
</a>
|
||||
@@ -62,7 +64,7 @@
|
||||
replied
|
||||
</a>
|
||||
<span class="timeago" title="{topics.teaser_timestamp}"></span>
|
||||
|
||||
<!-- ENDIF topics.unreplied -->
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">{topic_name} <a target="_blank" href="../{topic_id}.rss"><i class="icon-rss-sign"></i></a></span>
|
||||
</li>
|
||||
<div class="thread_active_users active-users pull-right hidden-xs"></div>
|
||||
|
||||
</ol>
|
||||
|
||||
<ul id="post-container" class="container" data-tid="{topic_id}">
|
||||
<!-- BEGIN main_posts -->
|
||||
<a id="post_anchor_{main_posts.pid}" name="{main_posts.pid}"></a>
|
||||
<li class="row post-row main-post" data-pid="{main_posts.pid}" data-uid="{main_posts.uid}" data-username="{main_posts.username}" data-deleted="{main_posts.deleted}" itemscope itemtype="http://schema.org/Article">
|
||||
|
||||
<li class="row post-row main-post infiniteloaded" data-pid="{main_posts.pid}" data-uid="{main_posts.uid}" data-username="{main_posts.username}" data-deleted="{main_posts.deleted}" itemscope itemtype="http://schema.org/Article">
|
||||
<a id="post_anchor_{main_posts.pid}" name="{main_posts.pid}"></a>
|
||||
<div class="col-md-12">
|
||||
<div class="post-block">
|
||||
<meta itemprop="datePublished" content="{main_posts.relativeTime}">
|
||||
@@ -63,17 +64,17 @@
|
||||
<button class="btn btn-sm btn-primary btn post_reply" type="button">[[topic:reply]] <i class="icon-reply"></i></button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="btn-group pull-right post-tools">
|
||||
<button class="btn btn-sm btn-default edit {main_posts.display_moderator_tools}" type="button" title="[[topic:edit]]"><i class="icon-pencil"></i></button>
|
||||
<button class="btn btn-sm btn-default delete {main_posts.display_moderator_tools}" type="button" title="[[topic:delete]]"><i class="icon-trash"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group pull-right post-tools">
|
||||
<button class="btn btn-sm btn-default link" type="button" title="[[topic:link]]"><i class="icon-link"></i></button>
|
||||
<button class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="icon-facebook"></i></button>
|
||||
<button class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="icon-twitter"></i></button>
|
||||
<button class="btn btn-sm btn-default google-share" type="button" title=""><i class="icon-google-plus"></i></button>
|
||||
<div class="pull-right">
|
||||
<div class="btn-group post-tools">
|
||||
<button class="btn btn-sm btn-default link" type="button" title="[[topic:link]]"><i class="icon-link"></i></button>
|
||||
<button class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="icon-facebook"></i></button>
|
||||
<button class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="icon-twitter"></i></button>
|
||||
<button class="btn btn-sm btn-default google-share" type="button" title=""><i class="icon-google-plus"></i></button>
|
||||
</div>
|
||||
<div class="btn-group post-tools">
|
||||
<button class="btn btn-sm btn-default edit {main_posts.display_moderator_tools}" type="button" title="[[topic:edit]]"><i class="icon-pencil"></i></button>
|
||||
<button class="btn btn-sm btn-default delete {main_posts.display_moderator_tools}" type="button" title="[[topic:delete]]"><i class="icon-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input id="post_{main_posts.pid}_link" value="" class="pull-right" style="display:none;"></input>
|
||||
@@ -98,9 +99,38 @@
|
||||
</li>
|
||||
<!-- END main_posts -->
|
||||
|
||||
<li class="well">
|
||||
<div class="inline-block">
|
||||
<small class="topic-stats">
|
||||
<span>posts</span>
|
||||
<strong><span id="topic-post-count" class="formatted-number">{postcount}</span></strong> |
|
||||
<span>views</span>
|
||||
<strong><span class="formatted-number">{viewcount}</span></strong> |
|
||||
<span>browsing</span>
|
||||
</small>
|
||||
<div class="thread_active_users active-users inline-block"></div>
|
||||
</div>
|
||||
<div class="topic-main-buttons pull-right inline-block">
|
||||
<button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button>
|
||||
<div class="btn-group thread-tools hide">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" class="pin_thread"><i class="icon-pushpin"></i> [[topic:thread_tools.pin]]</a></li>
|
||||
<li><a href="#" class="lock_thread"><i class="icon-lock"></i> [[topic:thread_tools.lock]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="move_thread"><i class="icon-move"></i> [[topic:thread_tools.move]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="delete_thread"><span class="text-error"><i class="icon-trash"></i> [[topic:thread_tools.delete]]</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;"></div>
|
||||
</li>
|
||||
|
||||
<!-- BEGIN posts -->
|
||||
<a id="post_anchor_{posts.pid}" name="{posts.pid}"></a>
|
||||
<li class="row post-row sub-posts" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment">
|
||||
|
||||
<li class="row post-row sub-posts infiniteloaded" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment">
|
||||
<a id="post_anchor_{posts.pid}" name="{posts.pid}"></a>
|
||||
<meta itemprop="datePublished" content="{posts.relativeTime}">
|
||||
<meta itemprop="dateModified" content="{posts.relativeEditTime}">
|
||||
<div class="col-md-1 profile-image-block hidden-xs hidden-sm">
|
||||
@@ -163,25 +193,25 @@
|
||||
<!-- END posts -->
|
||||
</ul>
|
||||
|
||||
<div id="loading-indicator" style="text-align:center;" class="hide" done="0">
|
||||
<i class="icon-spinner icon-spin icon-large"></i>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="topic-main-buttons">
|
||||
<button id="post_reply" class="btn btn-primary btn-lg post_reply" type="button">[[topic:reply]]</button>
|
||||
<div class="btn-group pull-right" id="thread-tools" style="visibility: hidden;">
|
||||
<button class="btn btn-default btn-lg dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" id="pin_thread"><i class="icon-pushpin"></i> [[topic:thread_tools.pin]]</a></li>
|
||||
<li><a href="#" id="lock_thread"><i class="icon-lock"></i> [[topic:thread_tools.lock]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" id="move_thread"><i class="icon-move"></i> [[topic:thread_tools.move]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" id="delete_thread"><span class="text-error"><i class="icon-trash"></i> [[topic:thread_tools.delete]]</span></a></li>
|
||||
</ul>
|
||||
<div class="well col-md-11 col-xs-12 pull-right hide">
|
||||
<div class="topic-main-buttons pull-right inline-block hide">
|
||||
<div class="loading-indicator" done="0" style="display:none;">
|
||||
Loading More Posts <i class="icon-refresh icon-spin"></i>
|
||||
</div>
|
||||
<button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button>
|
||||
<div class="btn-group thread-tools hide">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" class="pin_thread"><i class="icon-pushpin"></i> [[topic:thread_tools.pin]]</a></li>
|
||||
<li><a href="#" class="lock_thread"><i class="icon-lock"></i> [[topic:thread_tools.lock]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="move_thread"><i class="icon-move"></i> [[topic:thread_tools.move]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="delete_thread"><span class="text-error"><i class="icon-trash"></i> [[topic:thread_tools.delete]]</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-author-overlay">
|
||||
|
||||
@@ -49,14 +49,17 @@
|
||||
</span>
|
||||
|
||||
<span class="pull-right hidden-xs">
|
||||
<!-- IF topics.unreplied -->
|
||||
No one has replied
|
||||
<!-- ELSE -->
|
||||
<a href="/user/{topics.teaser_userslug}">
|
||||
<img class="img-rounded teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
|
||||
<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
|
||||
</a>
|
||||
<a href="../../topic/{topics.slug}#{topics.teaser_pid}">
|
||||
replied
|
||||
</a>
|
||||
<span class="timeago" title="{topics.teaser_timestamp}"></span>
|
||||
|
||||
<!-- ENDIF topics.unreplied -->
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="search-user" type="text" placeholder="[[users:enter_username]]"/>
|
||||
<span class="input-group-addon">
|
||||
<span id="user-notfound-notify"><i class="icon icon-circle-blank"></i></span>
|
||||
<span id="user-notfound-notify"><i class="icon icon-search"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
162
public/vendor/colorpicker/colorpicker.css
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
.colorpicker {
|
||||
width: 356px;
|
||||
height: 176px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
background: url(./images/custom_background.png);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
display: none;
|
||||
}
|
||||
.colorpicker_color {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
left: 14px;
|
||||
top: 13px;
|
||||
position: absolute;
|
||||
background: #f00;
|
||||
overflow: hidden;
|
||||
cursor: crosshair;
|
||||
}
|
||||
.colorpicker_color div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background: url(./images/colorpicker_overlay.png);
|
||||
}
|
||||
.colorpicker_color div div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
overflow: hidden;
|
||||
background: url(./images/colorpicker_select.gif);
|
||||
margin: -5px 0 0 -5px;
|
||||
}
|
||||
.colorpicker_hue {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
left: 171px;
|
||||
width: 35px;
|
||||
height: 150px;
|
||||
cursor: n-resize;
|
||||
}
|
||||
.colorpicker_hue div {
|
||||
position: absolute;
|
||||
width: 35px;
|
||||
height: 9px;
|
||||
overflow: hidden;
|
||||
background: url(./images/custom_indic.gif) left top;
|
||||
margin: -4px 0 0 0;
|
||||
left: 0px;
|
||||
}
|
||||
.colorpicker_new_color {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
left: 213px;
|
||||
top: 13px;
|
||||
background: #f00;
|
||||
}
|
||||
.colorpicker_current_color {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
left: 283px;
|
||||
top: 13px;
|
||||
background: #f00;
|
||||
}
|
||||
.colorpicker input {
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: #898989;
|
||||
top: 4px;
|
||||
right: 11px;
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 11px;
|
||||
}
|
||||
.colorpicker_hex {
|
||||
position: absolute;
|
||||
width: 72px;
|
||||
height: 22px;
|
||||
background: url(./images/custom_hex.png) top;
|
||||
left: 212px;
|
||||
top: 142px;
|
||||
}
|
||||
.colorpicker_hex input {
|
||||
right: 6px;
|
||||
}
|
||||
.colorpicker_field {
|
||||
height: 22px;
|
||||
width: 62px;
|
||||
background-position: top;
|
||||
position: absolute;
|
||||
}
|
||||
.colorpicker_field span {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 22px;
|
||||
overflow: hidden;
|
||||
top: 0;
|
||||
right: 0;
|
||||
cursor: n-resize;
|
||||
}
|
||||
.colorpicker_rgb_r {
|
||||
background-image: url(./images/custom_rgb_r.png);
|
||||
top: 52px;
|
||||
left: 212px;
|
||||
}
|
||||
.colorpicker_rgb_g {
|
||||
background-image: url(./images/custom_rgb_g.png);
|
||||
top: 82px;
|
||||
left: 212px;
|
||||
}
|
||||
.colorpicker_rgb_b {
|
||||
background-image: url(./images/custom_rgb_b.png);
|
||||
top: 112px;
|
||||
left: 212px;
|
||||
}
|
||||
.colorpicker_hsb_h {
|
||||
background-image: url(./images/custom_hsb_h.png);
|
||||
top: 52px;
|
||||
left: 282px;
|
||||
}
|
||||
.colorpicker_hsb_s {
|
||||
background-image: url(./images/custom_hsb_s.png);
|
||||
top: 82px;
|
||||
left: 282px;
|
||||
}
|
||||
.colorpicker_hsb_b {
|
||||
background-image: url(./images/custom_hsb_b.png);
|
||||
top: 112px;
|
||||
left: 282px;
|
||||
}
|
||||
.colorpicker_submit {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: url(./images/custom_submit.png) top;
|
||||
left: 322px;
|
||||
top: 142px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.colorpicker_focus {
|
||||
background-position: center;
|
||||
}
|
||||
.colorpicker_hex.colorpicker_focus {
|
||||
background-position: bottom;
|
||||
}
|
||||
.colorpicker_submit.colorpicker_focus {
|
||||
background-position: bottom;
|
||||
}
|
||||
.colorpicker_slider {
|
||||
background-position: bottom;
|
||||
}
|
||||
484
public/vendor/colorpicker/colorpicker.js
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
/**
|
||||
*
|
||||
* Color picker
|
||||
* Author: Stefan Petre www.eyecon.ro
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses
|
||||
*
|
||||
*/
|
||||
(function ($) {
|
||||
var ColorPicker = function () {
|
||||
var
|
||||
ids = {},
|
||||
inAction,
|
||||
charMin = 65,
|
||||
visible,
|
||||
tpl = '<div class="colorpicker"><div class="colorpicker_color"><div><div></div></div></div><div class="colorpicker_hue"><div></div></div><div class="colorpicker_new_color"></div><div class="colorpicker_current_color"></div><div class="colorpicker_hex"><input type="text" maxlength="6" size="6" /></div><div class="colorpicker_rgb_r colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_g colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_h colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_s colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_submit"></div></div>',
|
||||
defaults = {
|
||||
eventName: 'click',
|
||||
onShow: function () {},
|
||||
onBeforeShow: function(){},
|
||||
onHide: function () {},
|
||||
onChange: function () {},
|
||||
onSubmit: function () {},
|
||||
color: 'ff0000',
|
||||
livePreview: true,
|
||||
flat: false
|
||||
},
|
||||
fillRGBFields = function (hsb, cal) {
|
||||
var rgb = HSBToRGB(hsb);
|
||||
$(cal).data('colorpicker').fields
|
||||
.eq(1).val(rgb.r).end()
|
||||
.eq(2).val(rgb.g).end()
|
||||
.eq(3).val(rgb.b).end();
|
||||
},
|
||||
fillHSBFields = function (hsb, cal) {
|
||||
$(cal).data('colorpicker').fields
|
||||
.eq(4).val(hsb.h).end()
|
||||
.eq(5).val(hsb.s).end()
|
||||
.eq(6).val(hsb.b).end();
|
||||
},
|
||||
fillHexFields = function (hsb, cal) {
|
||||
$(cal).data('colorpicker').fields
|
||||
.eq(0).val(HSBToHex(hsb)).end();
|
||||
},
|
||||
setSelector = function (hsb, cal) {
|
||||
$(cal).data('colorpicker').selector.css('backgroundColor', '#' + HSBToHex({h: hsb.h, s: 100, b: 100}));
|
||||
$(cal).data('colorpicker').selectorIndic.css({
|
||||
left: parseInt(150 * hsb.s/100, 10),
|
||||
top: parseInt(150 * (100-hsb.b)/100, 10)
|
||||
});
|
||||
},
|
||||
setHue = function (hsb, cal) {
|
||||
$(cal).data('colorpicker').hue.css('top', parseInt(150 - 150 * hsb.h/360, 10));
|
||||
},
|
||||
setCurrentColor = function (hsb, cal) {
|
||||
$(cal).data('colorpicker').currentColor.css('backgroundColor', '#' + HSBToHex(hsb));
|
||||
},
|
||||
setNewColor = function (hsb, cal) {
|
||||
$(cal).data('colorpicker').newColor.css('backgroundColor', '#' + HSBToHex(hsb));
|
||||
},
|
||||
keyDown = function (ev) {
|
||||
var pressedKey = ev.charCode || ev.keyCode || -1;
|
||||
if ((pressedKey > charMin && pressedKey <= 90) || pressedKey == 32) {
|
||||
return false;
|
||||
}
|
||||
var cal = $(this).parent().parent();
|
||||
if (cal.data('colorpicker').livePreview === true) {
|
||||
change.apply(this);
|
||||
}
|
||||
},
|
||||
change = function (ev) {
|
||||
var cal = $(this).parent().parent(), col;
|
||||
if (this.parentNode.className.indexOf('_hex') > 0) {
|
||||
cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value));
|
||||
} else if (this.parentNode.className.indexOf('_hsb') > 0) {
|
||||
cal.data('colorpicker').color = col = fixHSB({
|
||||
h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10),
|
||||
s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10),
|
||||
b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10)
|
||||
});
|
||||
} else {
|
||||
cal.data('colorpicker').color = col = RGBToHSB(fixRGB({
|
||||
r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10),
|
||||
g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10),
|
||||
b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10)
|
||||
}));
|
||||
}
|
||||
if (ev) {
|
||||
fillRGBFields(col, cal.get(0));
|
||||
fillHexFields(col, cal.get(0));
|
||||
fillHSBFields(col, cal.get(0));
|
||||
}
|
||||
setSelector(col, cal.get(0));
|
||||
setHue(col, cal.get(0));
|
||||
setNewColor(col, cal.get(0));
|
||||
cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]);
|
||||
},
|
||||
blur = function (ev) {
|
||||
var cal = $(this).parent().parent();
|
||||
cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus');
|
||||
},
|
||||
focus = function () {
|
||||
charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65;
|
||||
$(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus');
|
||||
$(this).parent().addClass('colorpicker_focus');
|
||||
},
|
||||
downIncrement = function (ev) {
|
||||
var field = $(this).parent().find('input').focus();
|
||||
var current = {
|
||||
el: $(this).parent().addClass('colorpicker_slider'),
|
||||
max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
|
||||
y: ev.pageY,
|
||||
field: field,
|
||||
val: parseInt(field.val(), 10),
|
||||
preview: $(this).parent().parent().data('colorpicker').livePreview
|
||||
};
|
||||
$(document).bind('mouseup', current, upIncrement);
|
||||
$(document).bind('mousemove', current, moveIncrement);
|
||||
},
|
||||
moveIncrement = function (ev) {
|
||||
ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10))));
|
||||
if (ev.data.preview) {
|
||||
change.apply(ev.data.field.get(0), [true]);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
upIncrement = function (ev) {
|
||||
change.apply(ev.data.field.get(0), [true]);
|
||||
ev.data.el.removeClass('colorpicker_slider').find('input').focus();
|
||||
$(document).unbind('mouseup', upIncrement);
|
||||
$(document).unbind('mousemove', moveIncrement);
|
||||
return false;
|
||||
},
|
||||
downHue = function (ev) {
|
||||
var current = {
|
||||
cal: $(this).parent(),
|
||||
y: $(this).offset().top
|
||||
};
|
||||
current.preview = current.cal.data('colorpicker').livePreview;
|
||||
$(document).bind('mouseup', current, upHue);
|
||||
$(document).bind('mousemove', current, moveHue);
|
||||
},
|
||||
moveHue = function (ev) {
|
||||
change.apply(
|
||||
ev.data.cal.data('colorpicker')
|
||||
.fields
|
||||
.eq(4)
|
||||
.val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10))
|
||||
.get(0),
|
||||
[ev.data.preview]
|
||||
);
|
||||
return false;
|
||||
},
|
||||
upHue = function (ev) {
|
||||
fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
|
||||
fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
|
||||
$(document).unbind('mouseup', upHue);
|
||||
$(document).unbind('mousemove', moveHue);
|
||||
return false;
|
||||
},
|
||||
downSelector = function (ev) {
|
||||
var current = {
|
||||
cal: $(this).parent(),
|
||||
pos: $(this).offset()
|
||||
};
|
||||
current.preview = current.cal.data('colorpicker').livePreview;
|
||||
$(document).bind('mouseup', current, upSelector);
|
||||
$(document).bind('mousemove', current, moveSelector);
|
||||
},
|
||||
moveSelector = function (ev) {
|
||||
change.apply(
|
||||
ev.data.cal.data('colorpicker')
|
||||
.fields
|
||||
.eq(6)
|
||||
.val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10))
|
||||
.end()
|
||||
.eq(5)
|
||||
.val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10))
|
||||
.get(0),
|
||||
[ev.data.preview]
|
||||
);
|
||||
return false;
|
||||
},
|
||||
upSelector = function (ev) {
|
||||
fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
|
||||
fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
|
||||
$(document).unbind('mouseup', upSelector);
|
||||
$(document).unbind('mousemove', moveSelector);
|
||||
return false;
|
||||
},
|
||||
enterSubmit = function (ev) {
|
||||
$(this).addClass('colorpicker_focus');
|
||||
},
|
||||
leaveSubmit = function (ev) {
|
||||
$(this).removeClass('colorpicker_focus');
|
||||
},
|
||||
clickSubmit = function (ev) {
|
||||
var cal = $(this).parent();
|
||||
var col = cal.data('colorpicker').color;
|
||||
cal.data('colorpicker').origColor = col;
|
||||
setCurrentColor(col, cal.get(0));
|
||||
cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el);
|
||||
},
|
||||
show = function (ev) {
|
||||
var cal = $('#' + $(this).data('colorpickerId'));
|
||||
cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]);
|
||||
var pos = $(this).offset();
|
||||
var viewPort = getViewport();
|
||||
var top = pos.top + this.offsetHeight;
|
||||
var left = pos.left;
|
||||
if (top + 176 > viewPort.t + viewPort.h) {
|
||||
top -= this.offsetHeight + 176;
|
||||
}
|
||||
if (left + 356 > viewPort.l + viewPort.w) {
|
||||
left -= 356;
|
||||
}
|
||||
cal.css({left: left + 'px', top: top + 'px'});
|
||||
if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) {
|
||||
cal.show();
|
||||
}
|
||||
$(document).bind('mousedown', {cal: cal}, hide);
|
||||
return false;
|
||||
},
|
||||
hide = function (ev) {
|
||||
if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) {
|
||||
if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) {
|
||||
ev.data.cal.hide();
|
||||
}
|
||||
$(document).unbind('mousedown', hide);
|
||||
}
|
||||
},
|
||||
isChildOf = function(parentEl, el, container) {
|
||||
if (parentEl == el) {
|
||||
return true;
|
||||
}
|
||||
if (parentEl.contains) {
|
||||
return parentEl.contains(el);
|
||||
}
|
||||
if ( parentEl.compareDocumentPosition ) {
|
||||
return !!(parentEl.compareDocumentPosition(el) & 16);
|
||||
}
|
||||
var prEl = el.parentNode;
|
||||
while(prEl && prEl != container) {
|
||||
if (prEl == parentEl)
|
||||
return true;
|
||||
prEl = prEl.parentNode;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getViewport = function () {
|
||||
var m = document.compatMode == 'CSS1Compat';
|
||||
return {
|
||||
l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
|
||||
t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop),
|
||||
w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth),
|
||||
h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight)
|
||||
};
|
||||
},
|
||||
fixHSB = function (hsb) {
|
||||
return {
|
||||
h: Math.min(360, Math.max(0, hsb.h)),
|
||||
s: Math.min(100, Math.max(0, hsb.s)),
|
||||
b: Math.min(100, Math.max(0, hsb.b))
|
||||
};
|
||||
},
|
||||
fixRGB = function (rgb) {
|
||||
return {
|
||||
r: Math.min(255, Math.max(0, rgb.r)),
|
||||
g: Math.min(255, Math.max(0, rgb.g)),
|
||||
b: Math.min(255, Math.max(0, rgb.b))
|
||||
};
|
||||
},
|
||||
fixHex = function (hex) {
|
||||
var len = 6 - hex.length;
|
||||
if (len > 0) {
|
||||
var o = [];
|
||||
for (var i=0; i<len; i++) {
|
||||
o.push('0');
|
||||
}
|
||||
o.push(hex);
|
||||
hex = o.join('');
|
||||
}
|
||||
return hex;
|
||||
},
|
||||
HexToRGB = function (hex) {
|
||||
var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
|
||||
return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
|
||||
},
|
||||
HexToHSB = function (hex) {
|
||||
return RGBToHSB(HexToRGB(hex));
|
||||
},
|
||||
RGBToHSB = function (rgb) {
|
||||
var hsb = {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
};
|
||||
var min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
var max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
var delta = max - min;
|
||||
hsb.b = max;
|
||||
if (max != 0) {
|
||||
|
||||
}
|
||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) {
|
||||
hsb.h = (rgb.g - rgb.b) / delta;
|
||||
} else if (rgb.g == max) {
|
||||
hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
} else {
|
||||
hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
}
|
||||
} else {
|
||||
hsb.h = -1;
|
||||
}
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) {
|
||||
hsb.h += 360;
|
||||
}
|
||||
hsb.s *= 100/255;
|
||||
hsb.b *= 100/255;
|
||||
return hsb;
|
||||
},
|
||||
HSBToRGB = function (hsb) {
|
||||
var rgb = {};
|
||||
var h = Math.round(hsb.h);
|
||||
var s = Math.round(hsb.s*255/100);
|
||||
var v = Math.round(hsb.b*255/100);
|
||||
if(s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
var t1 = v;
|
||||
var t2 = (255-s)*v/255;
|
||||
var t3 = (t1-t2)*(h%60)/60;
|
||||
if(h==360) h = 0;
|
||||
if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3}
|
||||
else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3}
|
||||
else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3}
|
||||
else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3}
|
||||
else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3}
|
||||
else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3}
|
||||
else {rgb.r=0; rgb.g=0; rgb.b=0}
|
||||
}
|
||||
return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
|
||||
},
|
||||
RGBToHex = function (rgb) {
|
||||
var hex = [
|
||||
rgb.r.toString(16),
|
||||
rgb.g.toString(16),
|
||||
rgb.b.toString(16)
|
||||
];
|
||||
$.each(hex, function (nr, val) {
|
||||
if (val.length == 1) {
|
||||
hex[nr] = '0' + val;
|
||||
}
|
||||
});
|
||||
return hex.join('');
|
||||
},
|
||||
HSBToHex = function (hsb) {
|
||||
return RGBToHex(HSBToRGB(hsb));
|
||||
},
|
||||
restoreOriginal = function () {
|
||||
var cal = $(this).parent();
|
||||
var col = cal.data('colorpicker').origColor;
|
||||
cal.data('colorpicker').color = col;
|
||||
fillRGBFields(col, cal.get(0));
|
||||
fillHexFields(col, cal.get(0));
|
||||
fillHSBFields(col, cal.get(0));
|
||||
setSelector(col, cal.get(0));
|
||||
setHue(col, cal.get(0));
|
||||
setNewColor(col, cal.get(0));
|
||||
};
|
||||
return {
|
||||
init: function (opt) {
|
||||
opt = $.extend({}, defaults, opt||{});
|
||||
if (typeof opt.color == 'string') {
|
||||
opt.color = HexToHSB(opt.color);
|
||||
} else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) {
|
||||
opt.color = RGBToHSB(opt.color);
|
||||
} else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) {
|
||||
opt.color = fixHSB(opt.color);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
return this.each(function () {
|
||||
if (!$(this).data('colorpickerId')) {
|
||||
var options = $.extend({}, opt);
|
||||
options.origColor = opt.color;
|
||||
var id = 'collorpicker_' + parseInt(Math.random() * 1000);
|
||||
$(this).data('colorpickerId', id);
|
||||
var cal = $(tpl).attr('id', id);
|
||||
if (options.flat) {
|
||||
cal.appendTo(this).show();
|
||||
} else {
|
||||
cal.appendTo(document.body);
|
||||
}
|
||||
options.fields = cal
|
||||
.find('input')
|
||||
.bind('keyup', keyDown)
|
||||
.bind('change', change)
|
||||
.bind('blur', blur)
|
||||
.bind('focus', focus);
|
||||
cal
|
||||
.find('span').bind('mousedown', downIncrement).end()
|
||||
.find('>div.colorpicker_current_color').bind('click', restoreOriginal);
|
||||
options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector);
|
||||
options.selectorIndic = options.selector.find('div div');
|
||||
options.el = this;
|
||||
options.hue = cal.find('div.colorpicker_hue div');
|
||||
cal.find('div.colorpicker_hue').bind('mousedown', downHue);
|
||||
options.newColor = cal.find('div.colorpicker_new_color');
|
||||
options.currentColor = cal.find('div.colorpicker_current_color');
|
||||
cal.data('colorpicker', options);
|
||||
cal.find('div.colorpicker_submit')
|
||||
.bind('mouseenter', enterSubmit)
|
||||
.bind('mouseleave', leaveSubmit)
|
||||
.bind('click', clickSubmit);
|
||||
fillRGBFields(options.color, cal.get(0));
|
||||
fillHSBFields(options.color, cal.get(0));
|
||||
fillHexFields(options.color, cal.get(0));
|
||||
setHue(options.color, cal.get(0));
|
||||
setSelector(options.color, cal.get(0));
|
||||
setCurrentColor(options.color, cal.get(0));
|
||||
setNewColor(options.color, cal.get(0));
|
||||
if (options.flat) {
|
||||
cal.css({
|
||||
position: 'relative',
|
||||
display: 'block'
|
||||
});
|
||||
} else {
|
||||
$(this).bind(options.eventName, show);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
showPicker: function() {
|
||||
return this.each( function () {
|
||||
if ($(this).data('colorpickerId')) {
|
||||
show.apply(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
hidePicker: function() {
|
||||
return this.each( function () {
|
||||
if ($(this).data('colorpickerId')) {
|
||||
$('#' + $(this).data('colorpickerId')).hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
setColor: function(col) {
|
||||
if (typeof col == 'string') {
|
||||
col = HexToHSB(col);
|
||||
} else if (col.r != undefined && col.g != undefined && col.b != undefined) {
|
||||
col = RGBToHSB(col);
|
||||
} else if (col.h != undefined && col.s != undefined && col.b != undefined) {
|
||||
col = fixHSB(col);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
return this.each(function(){
|
||||
if ($(this).data('colorpickerId')) {
|
||||
var cal = $('#' + $(this).data('colorpickerId'));
|
||||
cal.data('colorpicker').color = col;
|
||||
cal.data('colorpicker').origColor = col;
|
||||
fillRGBFields(col, cal.get(0));
|
||||
fillHSBFields(col, cal.get(0));
|
||||
fillHexFields(col, cal.get(0));
|
||||
setHue(col, cal.get(0));
|
||||
setSelector(col, cal.get(0));
|
||||
setCurrentColor(col, cal.get(0));
|
||||
setNewColor(col, cal.get(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}();
|
||||
$.fn.extend({
|
||||
ColorPicker: ColorPicker.init,
|
||||
ColorPickerHide: ColorPicker.hidePicker,
|
||||
ColorPickerShow: ColorPicker.showPicker,
|
||||
ColorPickerSetColor: ColorPicker.setColor
|
||||
});
|
||||
})(jQuery)
|
||||
BIN
public/vendor/colorpicker/images/Thumbs.db
vendored
Normal file
BIN
public/vendor/colorpicker/images/blank.gif
vendored
Normal file
|
After Width: | Height: | Size: 49 B |
BIN
public/vendor/colorpicker/images/colorpicker_background.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/vendor/colorpicker/images/colorpicker_hex.png
vendored
Normal file
|
After Width: | Height: | Size: 532 B |
BIN
public/vendor/colorpicker/images/colorpicker_hsb_b.png
vendored
Normal file
|
After Width: | Height: | Size: 970 B |
BIN
public/vendor/colorpicker/images/colorpicker_hsb_h.png
vendored
Normal file
|
After Width: | Height: | Size: 1012 B |
BIN
public/vendor/colorpicker/images/colorpicker_hsb_s.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/vendor/colorpicker/images/colorpicker_indic.gif
vendored
Normal file
|
After Width: | Height: | Size: 86 B |
BIN
public/vendor/colorpicker/images/colorpicker_overlay.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/vendor/colorpicker/images/colorpicker_rgb_b.png
vendored
Normal file
|
After Width: | Height: | Size: 970 B |
BIN
public/vendor/colorpicker/images/colorpicker_rgb_g.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/vendor/colorpicker/images/colorpicker_rgb_r.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/vendor/colorpicker/images/colorpicker_select.gif
vendored
Normal file
|
After Width: | Height: | Size: 78 B |
BIN
public/vendor/colorpicker/images/colorpicker_submit.png
vendored
Normal file
|
After Width: | Height: | Size: 984 B |
BIN
public/vendor/colorpicker/images/custom_background.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/vendor/colorpicker/images/custom_hex.png
vendored
Normal file
|
After Width: | Height: | Size: 562 B |
BIN
public/vendor/colorpicker/images/custom_hsb_b.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/vendor/colorpicker/images/custom_hsb_h.png
vendored
Normal file
|
After Width: | Height: | Size: 970 B |
BIN
public/vendor/colorpicker/images/custom_hsb_s.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/vendor/colorpicker/images/custom_indic.gif
vendored
Normal file
|
After Width: | Height: | Size: 86 B |
BIN
public/vendor/colorpicker/images/custom_rgb_b.png
vendored
Normal file
|
After Width: | Height: | Size: 1008 B |
BIN
public/vendor/colorpicker/images/custom_rgb_g.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/vendor/colorpicker/images/custom_rgb_r.png
vendored
Normal file
|
After Width: | Height: | Size: 1018 B |
BIN
public/vendor/colorpicker/images/custom_submit.png
vendored
Normal file
|
After Width: | Height: | Size: 997 B |
BIN
public/vendor/colorpicker/images/select.png
vendored
Normal file
|
After Width: | Height: | Size: 506 B |
BIN
public/vendor/colorpicker/images/select2.png
vendored
Normal file
|
After Width: | Height: | Size: 518 B |
BIN
public/vendor/colorpicker/images/slider.png
vendored
Normal file
|
After Width: | Height: | Size: 315 B |
@@ -252,26 +252,28 @@ var RDB = require('./redis.js'),
|
||||
|
||||
Categories.moveRecentReplies = function(tid, oldCid, cid, callback) {
|
||||
function movePost(pid, callback) {
|
||||
posts.getPostField(pid, 'timestamp', function(timestamp) {
|
||||
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
RDB.zrem('categories:recent_posts:cid:' + oldCid, pid);
|
||||
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
topics.getPids(tid, function(err, pids) {
|
||||
if (!err) {
|
||||
async.each(pids, movePost, function(err) {
|
||||
if (!err) {
|
||||
callback(null, 1);
|
||||
} else {
|
||||
winston.err(err);
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
winston.err(err);
|
||||
callback(err, null);
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
async.each(pids, movePost, function(err) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
callback(null, 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -372,19 +374,6 @@ var RDB = require('./redis.js'),
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
function getPostCategory(pid, callback) {
|
||||
posts.getPostField(pid, 'tid', function(tid) {
|
||||
|
||||
topics.getTopicField(tid, 'cid', function(err, postCid) {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
return callback(null, postCid);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var index = 0,
|
||||
active = false;
|
||||
|
||||
@@ -393,7 +382,7 @@ var RDB = require('./redis.js'),
|
||||
return active === false && index < pids.length;
|
||||
},
|
||||
function(callback) {
|
||||
getPostCategory(pids[index], function(err, postCid) {
|
||||
posts.getCidByPid(pids[index], function(err, postCid) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -411,7 +400,6 @@ var RDB = require('./redis.js'),
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
|
||||
callback(null, active);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ var RDB = require('./redis.js'),
|
||||
return;
|
||||
}
|
||||
|
||||
posts.getPostFields(pid, ['uid', 'timestamp'], function (postData) {
|
||||
posts.getPostFields(pid, ['uid', 'timestamp'], function (err, postData) {
|
||||
|
||||
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
|
||||
if (hasFavourited === 0) {
|
||||
@@ -57,7 +57,7 @@ var RDB = require('./redis.js'),
|
||||
return;
|
||||
}
|
||||
|
||||
posts.getPostField(pid, 'uid', function (uid_of_poster) {
|
||||
posts.getPostField(pid, 'uid', function (err, uid_of_poster) {
|
||||
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
|
||||
if (hasFavourited === 1) {
|
||||
RDB.srem('pid:' + pid + ':users_favourited', uid);
|
||||
|
||||
113
src/feed.js
@@ -2,12 +2,14 @@
|
||||
var RDB = require('./redis.js'),
|
||||
posts = require('./posts.js'),
|
||||
topics = require('./topics.js'),
|
||||
categories = require('./categories'),
|
||||
|
||||
fs = require('fs'),
|
||||
rss = require('rss'),
|
||||
winston = require('winston'),
|
||||
path = require('path'),
|
||||
nconf = require('nconf'),
|
||||
categories = require('./categories');
|
||||
async = require('async');
|
||||
|
||||
Feed.defaults = {
|
||||
ttl: 60,
|
||||
@@ -26,43 +28,43 @@
|
||||
}
|
||||
|
||||
Feed.updateTopic = function (tid, callback) {
|
||||
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating RSS feeds for topic ' + tid);
|
||||
|
||||
topics.getTopicWithPosts(tid, 0, 0, -1, function (err, topicData) {
|
||||
if (err) return callback(new Error('topic-invalid'));
|
||||
|
||||
var feed = new rss({
|
||||
title: topicData.topic_name,
|
||||
description: topicData.main_posts[0].content,
|
||||
feed_url: Feed.defaults.baseUrl + '/topics/' + tid + '.rss',
|
||||
site_url: nconf.get('url') + 'topic/' + topicData.slug,
|
||||
image_url: topicData.main_posts[0].picture,
|
||||
author: topicData.main_posts[0].username,
|
||||
ttl: Feed.defaults.ttl
|
||||
}),
|
||||
title: topicData.topic_name,
|
||||
description: topicData.main_posts[0].content,
|
||||
feed_url: Feed.defaults.baseUrl + '/topics/' + tid + '.rss',
|
||||
site_url: nconf.get('url') + 'topic/' + topicData.slug,
|
||||
image_url: topicData.main_posts[0].picture,
|
||||
author: topicData.main_posts[0].username,
|
||||
ttl: Feed.defaults.ttl
|
||||
}),
|
||||
topic_posts = topicData.main_posts.concat(topicData.posts),
|
||||
title, postData, dateStamp;
|
||||
dateStamp;
|
||||
|
||||
// Add pubDate if topic contains posts
|
||||
if (topicData.main_posts.length > 0) feed.pubDate = new Date(parseInt(topicData.main_posts[0].timestamp, 10)).toUTCString();
|
||||
|
||||
for (var i = 0, ii = topic_posts.length; i < ii; i++) {
|
||||
if (topic_posts[i].deleted === '0') {
|
||||
postData = topic_posts[i];
|
||||
async.each(topic_posts, function(postData, next) {
|
||||
if (postData.deleted === '0') {
|
||||
dateStamp = new Date(parseInt(postData.edited === '0' ? postData.timestamp : postData.edited, 10)).toUTCString();
|
||||
title = 'Reply to ' + topicData.topic_name + ' on ' + dateStamp;
|
||||
|
||||
feed.item({
|
||||
title: title,
|
||||
title: 'Reply to ' + topicData.topic_name + ' on ' + dateStamp,
|
||||
description: postData.content,
|
||||
url: nconf.get('url') + 'topic/' + topicData.slug + '#' + postData.pid,
|
||||
author: postData.username,
|
||||
date: dateStamp
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Feed.saveFeed('feeds/topics/' + tid + '.rss', feed, function (err) {
|
||||
next();
|
||||
}, function() {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('[rss] Re-generated RSS Feed for tid ' + tid + '.');
|
||||
}
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
});
|
||||
@@ -70,40 +72,75 @@
|
||||
};
|
||||
|
||||
Feed.updateCategory = function (cid, callback) {
|
||||
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating RSS feeds for category ' + cid);
|
||||
categories.getCategoryById(cid, 0, function (err, categoryData) {
|
||||
if (err) return callback(new Error('category-invalid'));
|
||||
|
||||
var feed = new rss({
|
||||
title: categoryData.category_name,
|
||||
description: categoryData.category_description,
|
||||
feed_url: Feed.defaults.baseUrl + '/categories/' + cid + '.rss',
|
||||
site_url: nconf.get('url') + 'category/' + categoryData.category_id,
|
||||
ttl: Feed.defaults.ttl
|
||||
}),
|
||||
topics = categoryData.topics,
|
||||
title, topicData, dateStamp;
|
||||
title: categoryData.category_name,
|
||||
description: categoryData.category_description,
|
||||
feed_url: Feed.defaults.baseUrl + '/categories/' + cid + '.rss',
|
||||
site_url: nconf.get('url') + 'category/' + categoryData.category_id,
|
||||
ttl: Feed.defaults.ttl
|
||||
});
|
||||
|
||||
// Add pubDate if category has topics
|
||||
if (categoryData.topics.length > 0) feed.pubDate = new Date(parseInt(categoryData.topics[0].lastposttime, 10)).toUTCString();
|
||||
|
||||
for (var i = 0, ii = topics.length; i < ii; i++) {
|
||||
topicData = topics[i];
|
||||
dateStamp = new Date(parseInt(topicData.lastposttime, 10)).toUTCString();
|
||||
title = topics[i].title;
|
||||
|
||||
async.eachSeries(categoryData.topics, function(topicData, next) {
|
||||
feed.item({
|
||||
title: title,
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + 'topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: dateStamp
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
}
|
||||
|
||||
Feed.saveFeed('feeds/categories/' + cid + '.rss', feed, function (err) {
|
||||
if (callback) callback();
|
||||
next();
|
||||
}, function() {
|
||||
Feed.saveFeed('feeds/categories/' + cid + '.rss', feed, function (err) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('[rss] Re-generated RSS Feed for cid ' + cid + '.');
|
||||
}
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Feed.updateRecent = function(callback) {
|
||||
console.log('entered');
|
||||
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating Recent Posts RSS feed');
|
||||
topics.getLatestTopics(0, 0, 19, undefined, function (err, recentData) {
|
||||
var feed = new rss({
|
||||
title: 'Recently Active Topics',
|
||||
description: 'A list of topics that have been active within the past 24 hours',
|
||||
feed_url: Feed.defaults.baseUrl + '/recent.rss',
|
||||
site_url: nconf.get('url') + 'recent',
|
||||
ttl: Feed.defaults.ttl
|
||||
});
|
||||
|
||||
// Add pubDate if recent topics list contains topics
|
||||
if (recentData.topics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(recentData.topics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
async.eachSeries(recentData.topics, function(topicData, next) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + 'topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
next();
|
||||
}, function() {
|
||||
Feed.saveFeed('feeds/recent.rss', feed, function (err) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('[rss] Re-generated "recent posts" RSS Feed.');
|
||||
}
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}(exports));
|
||||
@@ -56,6 +56,8 @@
|
||||
results.base.count = results.users.length;
|
||||
results.base.members = results.users;
|
||||
|
||||
results.base.deletable = (results.base.gid !== '1');
|
||||
|
||||
callback(err, results.base);
|
||||
});
|
||||
},
|
||||
@@ -103,7 +105,9 @@
|
||||
});
|
||||
},
|
||||
destroy: function (gid, callback) {
|
||||
RDB.hset('gid:' + gid, 'deleted', '1', callback);
|
||||
if (gid !== 1) {
|
||||
RDB.hset('gid:' + gid, 'deleted', '1', callback);
|
||||
}
|
||||
},
|
||||
join: function (gid, uid, callback) {
|
||||
RDB.sadd('gid:' + gid + ':members', uid, callback);
|
||||
|
||||
29
src/imgur.js
@@ -1,12 +1,11 @@
|
||||
var request = require('request');
|
||||
var request = require('request'),
|
||||
winston = require('winston');
|
||||
|
||||
|
||||
(function (imgur) {
|
||||
"use strict";
|
||||
|
||||
var clientID = '';
|
||||
|
||||
imgur.upload = function (image, type, callback) {
|
||||
imgur.upload = function (clientID, image, type, callback) {
|
||||
var options = {
|
||||
url: 'https://api.imgur.com/3/upload.json',
|
||||
headers: {
|
||||
@@ -15,21 +14,27 @@ var request = require('request');
|
||||
};
|
||||
|
||||
var post = request.post(options, function (err, req, body) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
try {
|
||||
callback(err, JSON.parse(body));
|
||||
} catch (e) {
|
||||
callback(err, body);
|
||||
var response = JSON.parse(body);
|
||||
if(response.success) {
|
||||
callback(null, response.data);
|
||||
} else {
|
||||
callback(new Error(response.data.error.message), null);
|
||||
}
|
||||
} catch(e) {
|
||||
winston.error('Unable to parse Imgur json response. [' + body +']');
|
||||
callback(e, null);
|
||||
}
|
||||
});
|
||||
|
||||
var upload = post.form({
|
||||
post.form({
|
||||
type: type,
|
||||
image: image
|
||||
});
|
||||
};
|
||||
|
||||
imgur.setClientID = function (id) {
|
||||
clientID = id;
|
||||
};
|
||||
|
||||
}(exports));
|
||||
@@ -63,13 +63,14 @@ var async = require('async'),
|
||||
}
|
||||
|
||||
if (setupVal && setupVal instanceof Object) {
|
||||
if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:email']) {
|
||||
if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:password:confirm'] && setupVal['admin:email']) {
|
||||
install.values = setupVal;
|
||||
next();
|
||||
} else {
|
||||
winston.error('Required values are missing for automated setup:');
|
||||
if (!setupVal['admin:username']) winston.error(' admin:username');
|
||||
if (!setupVal['admin:password']) winston.error(' admin:password');
|
||||
if (!setupVal['admin:password:confirm']) winston.error(' admin:password:confirm');
|
||||
if (!setupVal['admin:email']) winston.error(' admin:email');
|
||||
process.exit();
|
||||
}
|
||||
@@ -252,6 +253,11 @@ var async = require('async'),
|
||||
}
|
||||
});
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
// Upgrading schema
|
||||
var Upgrade = require('./upgrade');
|
||||
Upgrade.upgrade(next);
|
||||
}
|
||||
], function (err) {
|
||||
if (err) {
|
||||
@@ -277,18 +283,32 @@ var async = require('async'),
|
||||
description: 'Administrator email address',
|
||||
pattern: /.+@.+/,
|
||||
required: true
|
||||
}, {
|
||||
}],
|
||||
passwordQuestions = [{
|
||||
name: 'password',
|
||||
description: 'Password',
|
||||
required: true,
|
||||
hidden: true,
|
||||
type: 'string'
|
||||
}, {
|
||||
name: 'password:confirm',
|
||||
description: 'Confirm Password',
|
||||
required: true,
|
||||
hidden: true,
|
||||
type: 'string'
|
||||
}],
|
||||
success = function(err, results) {
|
||||
if (!results) {
|
||||
return callback(new Error('aborted'));
|
||||
}
|
||||
|
||||
// Check if the passwords match
|
||||
if (results['password:confirm'] !== results.password) {
|
||||
winston.warn("Passwords did not match, please try again");
|
||||
// Re-prompt password questions.
|
||||
return retryPassword(results);
|
||||
}
|
||||
|
||||
nconf.set('bcrypt_rounds', 12);
|
||||
User.create(results.username, results.password, results.email, function (err, uid) {
|
||||
if (err) {
|
||||
@@ -306,14 +326,33 @@ var async = require('async'),
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
retryPassword = function (originalResults) {
|
||||
// Ask only the password questions
|
||||
prompt.get(passwordQuestions, function (err, results) {
|
||||
if (!results) {
|
||||
return callback(new Error('aborted'));
|
||||
}
|
||||
|
||||
// Update the original data with newly collected password
|
||||
originalResults.password = results.password;
|
||||
originalResults['password:confirm'] = results['password:confirm'];
|
||||
|
||||
// Send back to success to handle
|
||||
success(err, originalResults);
|
||||
});
|
||||
};
|
||||
|
||||
// Add the password questions
|
||||
questions = questions.concat(passwordQuestions);
|
||||
|
||||
if (!install.values) prompt.get(questions, success);
|
||||
else {
|
||||
var results = {
|
||||
username: install.values['admin:username'],
|
||||
email: install.values['admin:email'],
|
||||
password: install.values['admin:password']
|
||||
password: install.values['admin:password'],
|
||||
'password:confirm': install.values['admin:password:confirm']
|
||||
};
|
||||
|
||||
success(null, results);
|
||||
|
||||
77
src/meta.js
@@ -1,5 +1,6 @@
|
||||
var utils = require('./../public/src/utils.js'),
|
||||
RDB = require('./redis.js'),
|
||||
plugins = require('./plugins'),
|
||||
async = require('async'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
@@ -102,7 +103,9 @@ var utils = require('./../public/src/utils.js'),
|
||||
var themeData = {
|
||||
'theme:type': data.type,
|
||||
'theme:id': data.id,
|
||||
'theme:staticDir': ''
|
||||
'theme:staticDir': '',
|
||||
'theme:templates': '',
|
||||
'theme:src': ''
|
||||
};
|
||||
|
||||
switch(data.type) {
|
||||
@@ -205,44 +208,46 @@ var utils = require('./../public/src/utils.js'),
|
||||
],
|
||||
minFile: path.join(__dirname, '..', 'public/src/nodebb.min.js'),
|
||||
get: function (callback) {
|
||||
var mtime,
|
||||
jsPaths = this.scripts.map(function (jsPath) {
|
||||
return path.join(__dirname, '..', '/public', jsPath);
|
||||
});
|
||||
plugins.fireHook('filter:scripts.get', this.scripts, function(err, scripts) {
|
||||
var mtime,
|
||||
jsPaths = scripts.map(function (jsPath) {
|
||||
return path.join(__dirname, '..', '/public', jsPath);
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
async.parallel({
|
||||
mtime: function (next) {
|
||||
async.map(jsPaths, fs.stat, function (err, stats) {
|
||||
async.reduce(stats, 0, function (memo, item, callback) {
|
||||
mtime = +new Date(item.mtime);
|
||||
callback(null, mtime > memo ? mtime : memo);
|
||||
}, next);
|
||||
});
|
||||
},
|
||||
minFile: function (next) {
|
||||
if (!fs.existsSync(Meta.js.minFile)) {
|
||||
if (process.env.NODE_ENV === 'development') winston.warn('No minified client-side library found');
|
||||
return next(null, 0);
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
async.parallel({
|
||||
mtime: function (next) {
|
||||
async.map(jsPaths, fs.stat, function (err, stats) {
|
||||
async.reduce(stats, 0, function (memo, item, callback) {
|
||||
mtime = +new Date(item.mtime);
|
||||
callback(null, mtime > memo ? mtime : memo);
|
||||
}, next);
|
||||
});
|
||||
},
|
||||
minFile: function (next) {
|
||||
if (!fs.existsSync(Meta.js.minFile)) {
|
||||
if (process.env.NODE_ENV === 'development') winston.warn('No minified client-side library found');
|
||||
return next(null, 0);
|
||||
}
|
||||
|
||||
fs.stat(Meta.js.minFile, function (err, stat) {
|
||||
next(err, +new Date(stat.mtime));
|
||||
});
|
||||
}
|
||||
|
||||
fs.stat(Meta.js.minFile, function (err, stat) {
|
||||
next(err, +new Date(stat.mtime));
|
||||
});
|
||||
}
|
||||
}, function (err, results) {
|
||||
if (results.minFile > results.mtime) {
|
||||
if (process.env.NODE_ENV === 'development') winston.info('No changes to client-side libraries -- skipping minification');
|
||||
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
|
||||
} else {
|
||||
Meta.js.minify(function () {
|
||||
}, function (err, results) {
|
||||
if (results.minFile > results.mtime) {
|
||||
if (process.env.NODE_ENV === 'development') winston.info('No changes to client-side libraries -- skipping minification');
|
||||
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(null, this.scripts);
|
||||
}
|
||||
} else {
|
||||
Meta.js.minify(function () {
|
||||
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(null, scripts);
|
||||
}
|
||||
});
|
||||
},
|
||||
minify: function (callback) {
|
||||
var uglifyjs = require('uglify-js'),
|
||||
|
||||
@@ -284,7 +284,7 @@ var fs = require('fs'),
|
||||
winston.warn("Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors.")
|
||||
return next(err, null);
|
||||
}
|
||||
|
||||
|
||||
_self.isActive(config.id, function(err, active) {
|
||||
if (err) next(new Error('no-active-state'));
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ var RDB = require('./redis.js'),
|
||||
}
|
||||
|
||||
function getThreadPrivileges(next) {
|
||||
posts.getPostField(pid, 'tid', function(tid) {
|
||||
posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
threadTools.privileges(tid, uid, function(privileges) {
|
||||
next(null, privileges);
|
||||
});
|
||||
@@ -42,7 +42,7 @@ var RDB = require('./redis.js'),
|
||||
}
|
||||
|
||||
function isOwnPost(next) {
|
||||
posts.getPostField(pid, 'uid', function(author) {
|
||||
posts.getPostField(pid, 'uid', function(err, author) {
|
||||
next(null, parseInt(author, 10) === parseInt(uid, 10));
|
||||
});
|
||||
}
|
||||
@@ -87,7 +87,7 @@ var RDB = require('./redis.js'),
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
posts.getPostField(pid, 'tid', function(tid) {
|
||||
posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
PostTools.isMain(pid, tid, function(isMainPost) {
|
||||
if (isMainPost) {
|
||||
topics.setTopicField(tid, 'title', title);
|
||||
@@ -132,41 +132,47 @@ var RDB = require('./redis.js'),
|
||||
RDB.decr('totalpostcount');
|
||||
postSearch.remove(pid);
|
||||
|
||||
posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
|
||||
posts.getPostFields(pid, ['tid', 'uid'], function(err, postData) {
|
||||
RDB.hincrby('topic:' + postData.tid, 'postcount', -1);
|
||||
|
||||
user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) {
|
||||
RDB.zadd('users:postcount', postcount, postData.uid);
|
||||
});
|
||||
|
||||
io.sockets. in ('topic_' + postData.tid).emit('event:post_deleted', {
|
||||
pid: pid
|
||||
});
|
||||
|
||||
// Delete the thread if it is the last undeleted post
|
||||
threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) {
|
||||
if (err && err.message === 'no-undeleted-pids-found') {
|
||||
threadTools.delete(postData.tid, -1, function(err) {
|
||||
if (err) winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
|
||||
if (err) {
|
||||
winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
posts.getPostField(pid, 'timestamp', function(timestamp) {
|
||||
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
|
||||
topics.updateTimestamp(postData.tid, timestamp);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Feed.updateTopic(postData.tid);
|
||||
Feed.updateRecent();
|
||||
|
||||
callback();
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
PostTools.privileges(pid, uid, function(privileges) {
|
||||
if (privileges.editable) {
|
||||
success();
|
||||
posts.getPostField(pid, 'deleted', function(err, deleted) {
|
||||
if(deleted === '1') {
|
||||
return callback(new Error('Post already deleted!'));
|
||||
}
|
||||
|
||||
PostTools.privileges(pid, uid, function(privileges) {
|
||||
if (privileges.editable) {
|
||||
success();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
PostTools.restore = function(uid, pid, callback) {
|
||||
@@ -174,17 +180,13 @@ var RDB = require('./redis.js'),
|
||||
posts.setPostField(pid, 'deleted', 0);
|
||||
RDB.incr('totalpostcount');
|
||||
|
||||
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(postData) {
|
||||
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(err, postData) {
|
||||
RDB.hincrby('topic:' + postData.tid, 'postcount', 1);
|
||||
|
||||
user.incrementUserFieldBy(postData.uid, 'postcount', 1);
|
||||
|
||||
io.sockets. in ('topic_' + postData.tid).emit('event:post_restored', {
|
||||
pid: pid
|
||||
});
|
||||
|
||||
threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) {
|
||||
posts.getPostField(pid, 'timestamp', function(timestamp) {
|
||||
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
|
||||
topics.updateTimestamp(postData.tid, timestamp);
|
||||
});
|
||||
});
|
||||
@@ -197,6 +199,7 @@ var RDB = require('./redis.js'),
|
||||
});
|
||||
|
||||
Feed.updateTopic(postData.tid);
|
||||
Feed.updateRecent();
|
||||
|
||||
postSearch.index(postData.content, pid);
|
||||
|
||||
@@ -204,10 +207,16 @@ var RDB = require('./redis.js'),
|
||||
});
|
||||
};
|
||||
|
||||
PostTools.privileges(pid, uid, function(privileges) {
|
||||
if (privileges.editable) {
|
||||
success();
|
||||
}
|
||||
posts.getPostField(pid, 'deleted', function(err, deleted) {
|
||||
if(deleted === '0') {
|
||||
return callback(new Error('Post already restored'));
|
||||
}
|
||||
|
||||
PostTools.privileges(pid, uid, function(privileges) {
|
||||
if (privileges.editable) {
|
||||
success();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
430
src/posts.js
@@ -2,6 +2,7 @@ var RDB = require('./redis.js'),
|
||||
utils = require('./../public/src/utils.js'),
|
||||
user = require('./user.js'),
|
||||
topics = require('./topics.js'),
|
||||
categories = require('./categories.js'),
|
||||
favourites = require('./favourites.js'),
|
||||
threadTools = require('./threadTools.js'),
|
||||
postTools = require('./postTools'),
|
||||
@@ -13,11 +14,183 @@ var RDB = require('./redis.js'),
|
||||
postSearch = reds.createSearch('nodebbpostsearch'),
|
||||
nconf = require('nconf'),
|
||||
meta = require('./meta.js'),
|
||||
validator = require('validator'),
|
||||
winston = require('winston');
|
||||
|
||||
(function(Posts) {
|
||||
var customUserInfo = {};
|
||||
|
||||
Posts.create = function(uid, tid, content, callback) {
|
||||
if (uid === null) {
|
||||
callback(new Error('invalid-user'), null);
|
||||
return;
|
||||
}
|
||||
|
||||
topics.isLocked(tid, function(err, locked) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
} else if(locked) {
|
||||
callback(new Error('topic-locked'), null);
|
||||
}
|
||||
|
||||
RDB.incr('global:next_post_id', function(err, pid) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:post.save', content, function(err, newContent) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
content = newContent;
|
||||
|
||||
var timestamp = Date.now(),
|
||||
postData = {
|
||||
'pid': pid,
|
||||
'uid': uid,
|
||||
'tid': tid,
|
||||
'content': content,
|
||||
'timestamp': timestamp,
|
||||
'reputation': 0,
|
||||
'editor': '',
|
||||
'edited': 0,
|
||||
'deleted': 0,
|
||||
'fav_button_class': '',
|
||||
'fav_star_class': 'icon-star-empty',
|
||||
'show_banned': 'hide',
|
||||
'relativeTime': new Date(timestamp).toISOString(),
|
||||
'post_rep': '0',
|
||||
'edited-class': 'none',
|
||||
'relativeEditTime': ''
|
||||
};
|
||||
|
||||
RDB.hmset('post:' + pid, postData);
|
||||
|
||||
topics.addPostToTopic(tid, pid);
|
||||
topics.increasePostCount(tid);
|
||||
topics.updateTimestamp(tid, timestamp);
|
||||
|
||||
RDB.incr('totalpostcount');
|
||||
|
||||
topics.getTopicFields(tid, ['cid', 'pinned'], function(err, topicData) {
|
||||
|
||||
RDB.handle(err);
|
||||
|
||||
var cid = topicData.cid;
|
||||
|
||||
feed.updateTopic(tid);
|
||||
feed.updateRecent();
|
||||
|
||||
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
|
||||
|
||||
if(topicData.pinned === '0')
|
||||
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
|
||||
|
||||
RDB.scard('cid:' + cid + ':active_users', function(err, amount) {
|
||||
if (amount > 16) {
|
||||
RDB.spop('cid:' + cid + ':active_users');
|
||||
}
|
||||
|
||||
categories.addActiveUser(cid, uid);
|
||||
});
|
||||
});
|
||||
|
||||
user.onNewPostMade(uid, tid, pid, timestamp);
|
||||
|
||||
plugins.fireHook('filter:post.get', postData, function(err, newPostData) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
postData = newPostData;
|
||||
|
||||
postTools.parse(postData.content, function(err, content) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
postData.content = content;
|
||||
|
||||
plugins.fireHook('action:post.save', postData);
|
||||
|
||||
postSearch.index(content, pid);
|
||||
|
||||
callback(null, postData);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Posts.reply = function(tid, uid, content, callback) {
|
||||
if(content) {
|
||||
content = content.trim();
|
||||
}
|
||||
|
||||
if (!content || content.length < meta.config.minimumPostLength) {
|
||||
callback(new Error('content-too-short'), null);
|
||||
return;
|
||||
}
|
||||
|
||||
Posts.create(uid, tid, content, function(err, postData) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
} else if(!postData) {
|
||||
callback(new Error('reply-error'), null);
|
||||
}
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
topics.markUnRead(tid, function(err) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
topics.markAsRead(tid, uid);
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
Posts.getCidByPid(postData.pid, function(err, cid) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
RDB.del('cid:' + cid + ':read_by_uid');
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
threadTools.notifyFollowers(tid, uid);
|
||||
next();
|
||||
},
|
||||
function(next) {
|
||||
Posts.addUserInfoToPost(postData, function(err) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var socketData = {
|
||||
posts: [postData]
|
||||
};
|
||||
|
||||
io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
|
||||
io.sockets.in('recent_posts').emit('event:new_post', socketData);
|
||||
io.sockets.in('user/' + uid).emit('event:new_post', socketData);
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
], function(err, results) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
callback(null, 'Reply successful');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Posts.getPostsByTid = function(tid, start, end, callback) {
|
||||
RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) {
|
||||
RDB.handle(err);
|
||||
@@ -86,7 +259,7 @@ var RDB = require('./redis.js'),
|
||||
function getPostSummary(pid, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) {
|
||||
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(err, postData) {
|
||||
if (postData.deleted === '1') return callback(null);
|
||||
else {
|
||||
postData.relativeTime = new Date(parseInt(postData.timestamp || 0, 10)).toISOString();
|
||||
@@ -100,12 +273,15 @@ var RDB = require('./redis.js'),
|
||||
});
|
||||
},
|
||||
function(postData, next) {
|
||||
topics.getTopicFields(postData.tid, ['slug', 'deleted'], function(err, topicData) {
|
||||
topics.getTopicFields(postData.tid, ['title', 'cid', 'slug', 'deleted'], function(err, topicData) {
|
||||
if (err) return callback(err);
|
||||
else if (topicData.deleted === '1') return callback(null);
|
||||
|
||||
postData.topicSlug = topicData.slug;
|
||||
next(null, postData);
|
||||
categories.getCategoryField(topicData.cid, 'name', function(err, categoryData) {
|
||||
postData.category_name = categoryData;
|
||||
postData.title = validator.sanitize(topicData.title).escape();
|
||||
postData.topicSlug = topicData.slug;
|
||||
next(null, postData);
|
||||
})
|
||||
});
|
||||
},
|
||||
function(postData, next) {
|
||||
@@ -131,51 +307,48 @@ var RDB = require('./redis.js'),
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: this function is never called except from some debug route. clean up?
|
||||
Posts.getPostData = function(pid, callback) {
|
||||
RDB.hgetall('post:' + pid, function(err, data) {
|
||||
if (err === null) {
|
||||
plugins.fireHook('filter:post.get', data, function(err, newData) {
|
||||
if (!err) callback(newData);
|
||||
else callback(data);
|
||||
});
|
||||
} else {
|
||||
winston.error(err);
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:post.get', data, function(err, newData) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
callback(null, newData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Posts.getPostFields = function(pid, fields, callback) {
|
||||
RDB.hmgetObject('post:' + pid, fields, function(err, data) {
|
||||
if (err === null) {
|
||||
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
|
||||
data = data || {};
|
||||
data.pid = pid;
|
||||
data.fields = fields;
|
||||
|
||||
plugins.fireHook('filter:post.getFields', data, function(err, data) {
|
||||
callback(data);
|
||||
});
|
||||
} else {
|
||||
console.log(err);
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
|
||||
data = data || {};
|
||||
data.pid = pid;
|
||||
data.fields = fields;
|
||||
|
||||
plugins.fireHook('filter:post.getFields', data, function(err, data) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
callback(null, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Posts.getPostField = function(pid, field, callback) {
|
||||
RDB.hget('post:' + pid, field, function(err, data) {
|
||||
if (err === null) {
|
||||
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
|
||||
data = data || {};
|
||||
data.pid = pid;
|
||||
data.field = field;
|
||||
|
||||
plugins.fireHook('filter:post.getField', data, function(err, data) {
|
||||
callback(data);
|
||||
});
|
||||
} else {
|
||||
console.log(err);
|
||||
Posts.getPostFields(pid, [field], function(err, data) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
callback(null, data[field]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,8 +365,8 @@ var RDB = require('./redis.js'),
|
||||
var posts = [],
|
||||
multi = RDB.multi();
|
||||
|
||||
for(var x=0,numPids=pids.length;x<numPids;x++) {
|
||||
multi.hgetall("post:"+pids[x]);
|
||||
for(var x=0, numPids=pids.length; x<numPids; x++) {
|
||||
multi.hgetall("post:" + pids[x]);
|
||||
}
|
||||
|
||||
multi.exec(function (err, replies) {
|
||||
@@ -204,12 +377,11 @@ var RDB = require('./redis.js'),
|
||||
postData['edited-class'] = postData.editor !== '' ? '' : 'none';
|
||||
try {
|
||||
postData.relativeTime = new Date(parseInt(postData.timestamp,10)).toISOString();
|
||||
postData['relativeEditTime'] = postData.edited !== '0' ? (new Date(parseInt(postData.edited,10)).toISOString()) : '';
|
||||
postData.relativeEditTime = postData.edited !== '0' ? (new Date(parseInt(postData.edited,10)).toISOString()) : '';
|
||||
} catch(e) {
|
||||
winston.err('invalid time value');
|
||||
}
|
||||
|
||||
|
||||
if (postData.uploadedImages) {
|
||||
try {
|
||||
postData.uploadedImages = JSON.parse(postData.uploadedImages);
|
||||
@@ -238,17 +410,23 @@ var RDB = require('./redis.js'),
|
||||
})
|
||||
}
|
||||
|
||||
Posts.get_cid_by_pid = function(pid, callback) {
|
||||
Posts.getPostField(pid, 'tid', function(tid) {
|
||||
if (tid) {
|
||||
topics.getTopicField(tid, 'cid', function(err, cid) {
|
||||
if (cid) {
|
||||
callback(cid);
|
||||
} else {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
Posts.getCidByPid = function(pid, callback) {
|
||||
Posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
topics.getTopicField(tid, 'cid', function(err, cid) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
if (cid) {
|
||||
callback(null, cid);
|
||||
} else {
|
||||
callback(new Error('invalid-category-id'), null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -271,157 +449,19 @@ var RDB = require('./redis.js'),
|
||||
});
|
||||
}
|
||||
|
||||
Posts.reply = function(tid, uid, content, callback) {
|
||||
if(content) {
|
||||
content = content.trim();
|
||||
}
|
||||
|
||||
if (!content || content.length < meta.config.minimumPostLength) {
|
||||
callback(new Error('content-too-short'), null);
|
||||
return;
|
||||
}
|
||||
|
||||
Posts.create(uid, tid, content, function(postData) {
|
||||
if (postData) {
|
||||
|
||||
topics.markUnRead(tid);
|
||||
|
||||
Posts.get_cid_by_pid(postData.pid, function(cid) {
|
||||
RDB.del('cid:' + cid + ':read_by_uid', function(err, data) {
|
||||
topics.markAsRead(tid, uid);
|
||||
});
|
||||
});
|
||||
|
||||
threadTools.notifyFollowers(tid, uid);
|
||||
|
||||
Posts.addUserInfoToPost(postData, function() {
|
||||
var socketData = {
|
||||
posts: [postData]
|
||||
};
|
||||
|
||||
io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
|
||||
io.sockets.in('recent_posts').emit('event:new_post', socketData);
|
||||
io.sockets.in('user/' + uid).emit('event:new_post', socketData);
|
||||
});
|
||||
|
||||
callback(null, 'Reply successful');
|
||||
} else {
|
||||
callback(new Error('reply-error'), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Posts.create = function(uid, tid, content, callback) {
|
||||
if (uid === null) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
topics.isLocked(tid, function(locked) {
|
||||
if (!locked || locked === '0') {
|
||||
RDB.incr('global:next_post_id', function(err, pid) {
|
||||
RDB.handle(err);
|
||||
|
||||
plugins.fireHook('filter:post.save', content, function(err, newContent) {
|
||||
if (!err) content = newContent;
|
||||
|
||||
var timestamp = Date.now(),
|
||||
postData = {
|
||||
'pid': pid,
|
||||
'uid': uid,
|
||||
'tid': tid,
|
||||
'content': content,
|
||||
'timestamp': timestamp,
|
||||
'reputation': 0,
|
||||
'editor': '',
|
||||
'edited': 0,
|
||||
'deleted': 0,
|
||||
'fav_button_class': '',
|
||||
'fav_star_class': 'icon-star-empty',
|
||||
'show_banned': 'hide',
|
||||
'relativeTime': new Date(timestamp).toISOString(),
|
||||
'post_rep': '0',
|
||||
'edited-class': 'none',
|
||||
'relativeEditTime': ''
|
||||
};
|
||||
|
||||
RDB.hmset('post:' + pid, postData);
|
||||
|
||||
topics.addPostToTopic(tid, pid);
|
||||
topics.increasePostCount(tid);
|
||||
topics.updateTimestamp(tid, timestamp);
|
||||
|
||||
RDB.incr('totalpostcount');
|
||||
|
||||
topics.getTopicFields(tid, ['cid', 'pinned'], function(err, topicData) {
|
||||
|
||||
RDB.handle(err);
|
||||
|
||||
var cid = topicData.cid;
|
||||
|
||||
feed.updateTopic(tid);
|
||||
|
||||
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
|
||||
|
||||
if(topicData.pinned === '0')
|
||||
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
|
||||
|
||||
RDB.scard('cid:' + cid + ':active_users', function(err, amount) {
|
||||
if (amount > 10) {
|
||||
RDB.spop('cid:' + cid + ':active_users');
|
||||
}
|
||||
|
||||
categories.addActiveUser(cid, uid);
|
||||
});
|
||||
});
|
||||
|
||||
user.onNewPostMade(uid, tid, pid, timestamp);
|
||||
|
||||
async.parallel({
|
||||
content: function(next) {
|
||||
plugins.fireHook('filter:post.get', postData, function(err, newPostData) {
|
||||
if (!err) postData = newPostData;
|
||||
|
||||
postTools.parse(postData.content, function(err, content) {
|
||||
next(null, content);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, function(err, results) {
|
||||
postData.content = results.content;
|
||||
callback(postData);
|
||||
});
|
||||
|
||||
plugins.fireHook('action:post.save', postData);
|
||||
|
||||
postSearch.index(content, pid);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Posts.uploadPostImage = function(image, callback) {
|
||||
var imgur = require('./imgur');
|
||||
imgur.setClientID(meta.config.imgurClientID);
|
||||
|
||||
if(!image)
|
||||
return callback('invalid image', null);
|
||||
|
||||
imgur.upload(image.data, 'base64', function(err, data) {
|
||||
require('./imgur').upload(meta.config.imgurClientID, image.data, 'base64', function(err, data) {
|
||||
if(err) {
|
||||
callback('Can\'t upload image!', null);
|
||||
callback(err.message, null);
|
||||
} else {
|
||||
if(data.success) {
|
||||
var img= {url:data.data.link, name:image.name};
|
||||
|
||||
callback(null, img);
|
||||
} else {
|
||||
winston.error('Can\'t upload image, did you set imgurClientID?');
|
||||
callback("upload error", null);
|
||||
}
|
||||
callback(null, {
|
||||
url: data.link,
|
||||
name: image.name
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -462,7 +502,7 @@ var RDB = require('./redis.js'),
|
||||
|
||||
function reIndex(pid, callback) {
|
||||
|
||||
Posts.getPostField(pid, 'content', function(content) {
|
||||
Posts.getPostField(pid, 'content', function(err, content) {
|
||||
postSearch.remove(pid, function() {
|
||||
|
||||
if (content && content.length) {
|
||||
|
||||
20
src/redis.js
@@ -58,18 +58,18 @@
|
||||
*/
|
||||
RedisDB.hmgetObject = function(key, fields, callback) {
|
||||
RedisDB.hmget(key, fields, function(err, data) {
|
||||
if (err === null) {
|
||||
var returnData = {};
|
||||
|
||||
for (var i = 0, ii = fields.length; i < ii; ++i) {
|
||||
returnData[fields[i]] = data[i];
|
||||
}
|
||||
|
||||
callback(null, returnData);
|
||||
} else {
|
||||
console.log(err);
|
||||
callback(err, null);
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
var returnData = {};
|
||||
|
||||
for (var i = 0, ii = fields.length; i < ii; ++i) {
|
||||
returnData[fields[i]] = data[i];
|
||||
}
|
||||
|
||||
callback(null, returnData);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ var user = require('./../user.js'),
|
||||
}
|
||||
|
||||
var filename = 'site-logo' + extension;
|
||||
var uploadPath = path.join(process.cwd(), nconf.get('upload_path'), filename);
|
||||
var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename);
|
||||
|
||||
winston.info('Attempting upload to: ' + uploadPath);
|
||||
|
||||
@@ -246,7 +246,8 @@ var user = require('./../user.js'),
|
||||
app.get('/topics', function (req, res) {
|
||||
topics.getAllTopics(10, null, function (topics) {
|
||||
res.json({
|
||||
topics: topics
|
||||
topics: topics,
|
||||
notopics: topics.length === 0
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ var user = require('./../user.js'),
|
||||
config.maximumUsernameLength = meta.config.maximumUsernameLength;
|
||||
config.minimumPasswordLength = meta.config.minimumPasswordLength;
|
||||
config.useOutgoingLinksPage = meta.config.useOutgoingLinksPage;
|
||||
config.emailSetup = !!meta.config['email:from'];
|
||||
|
||||
res.json(200, config);
|
||||
});
|
||||
@@ -137,8 +138,12 @@ var user = require('./../user.js'),
|
||||
|
||||
app.get('/recent/:term?', function (req, res) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
topics.getLatestTopics(uid, 0, 19, req.params.term, function (data) {
|
||||
res.json(data);
|
||||
topics.getLatestTopics(uid, 0, 19, req.params.term, function (err, data) {
|
||||
if (!err) {
|
||||
res.json(data);
|
||||
} else {
|
||||
res.send(500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -189,7 +194,8 @@ var user = require('./../user.js'),
|
||||
|
||||
if (url) {
|
||||
res.json({
|
||||
url: url
|
||||
url: url,
|
||||
title: meta.config.title
|
||||
});
|
||||
} else {
|
||||
res.status(404);
|
||||
|
||||
@@ -1,50 +1,75 @@
|
||||
|
||||
var user = require('./../user'),
|
||||
categories = require('./../categories'),
|
||||
topics = require('./../topics'),
|
||||
posts = require('./../posts');
|
||||
|
||||
var DebugRoute = function(app) {
|
||||
app.namespace('/debug', function() {
|
||||
app.get('/cid/:cid', function (req, res) {
|
||||
categories.getCategoryData(req.params.cid, function (err, data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.send(404, "Category doesn't exist!");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/tid/:tid', function (req, res) {
|
||||
topics.getTopicData(req.params.tid, function (data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.send(404, "Topic doesn't exist!");
|
||||
}
|
||||
});
|
||||
});
|
||||
app.namespace('/debug', function() {
|
||||
|
||||
app.get('/pid/:pid', function (req, res) {
|
||||
posts.getPostData(req.params.pid, function (data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.send(404, "Post doesn't exist!");
|
||||
}
|
||||
});
|
||||
});
|
||||
app.get('/uid/:uid', function (req, res) {
|
||||
|
||||
app.get('/prune', function(req, res) {
|
||||
var Notifications = require('../notifications');
|
||||
if (!req.params.uid)
|
||||
return res.redirect('/404');
|
||||
|
||||
Notifications.prune(new Date(), function() {
|
||||
console.log('done');
|
||||
});
|
||||
res.send();
|
||||
});
|
||||
|
||||
app.get('/uuidtest', function(req, res) {
|
||||
var Utils = require('../../public/src/utils.js');
|
||||
|
||||
res.send(Utils.generateUUID());
|
||||
user.getUserData(req.params.uid, function (err, data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.json(404, {
|
||||
error: "User doesn't exist!"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
app.get('/cid/:cid', function (req, res) {
|
||||
categories.getCategoryData(req.params.cid, function (err, data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.send(404, "Category doesn't exist!");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/tid/:tid', function (req, res) {
|
||||
topics.getTopicData(req.params.tid, function (err, data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.send(404, "Topic doesn't exist!");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/pid/:pid', function (req, res) {
|
||||
posts.getPostData(req.params.pid, function (err, data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.send(404, "Post doesn't exist!");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/prune', function(req, res) {
|
||||
var Notifications = require('../notifications');
|
||||
|
||||
Notifications.prune(new Date(), function() {
|
||||
console.log('done');
|
||||
});
|
||||
res.send();
|
||||
});
|
||||
|
||||
app.get('/uuidtest', function(req, res) {
|
||||
var Utils = require('../../public/src/utils.js');
|
||||
|
||||
res.send(Utils.generateUUID());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = DebugRoute;
|
||||
@@ -1,9 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
var nconf = require('nconf'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
validator = require('validator'),
|
||||
Plugins = require('../plugins'),
|
||||
|
||||
PluginRoutes = function(app) {
|
||||
app.get('/plugins/fireHook', function(req, res) {
|
||||
// GET = filter
|
||||
Plugins.fireHook('filter:' + req.query.hook, req.query.args, function(err, returnData) {
|
||||
if (typeof returnData === 'object') {
|
||||
res.json(200, returnData);
|
||||
} else {
|
||||
res.send(200, validator.sanitize(returnData).escape());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.put('/plugins/fireHook', function(req, res) {
|
||||
// PUT = action
|
||||
Plugins.fireHook('action:' + req.body.hook, req.body.args);
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
// Static Assets
|
||||
app.get('/plugins/:id/*', function(req, res) {
|
||||
var relPath = req.url.replace('/plugins/' + req.params.id, '');
|
||||
@@ -15,7 +35,7 @@ var nconf = require('nconf'),
|
||||
} else {
|
||||
res.redirect('/404');
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
res.redirect('/404');
|
||||
}
|
||||
|
||||
@@ -14,22 +14,6 @@ var user = require('./../user.js'),
|
||||
(function (User) {
|
||||
User.createRoutes = function (app) {
|
||||
|
||||
app.get('/uid/:uid', function (req, res) {
|
||||
|
||||
if (!req.params.uid)
|
||||
return res.redirect('/404');
|
||||
|
||||
user.getUserData(req.params.uid, function (err, data) {
|
||||
if (data) {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.json(404, {
|
||||
error: "User doesn't exist!"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.namespace('/users', function () {
|
||||
app.get('', function (req, res) {
|
||||
app.build_header({
|
||||
@@ -175,7 +159,7 @@ var user = require('./../user.js'),
|
||||
return;
|
||||
}
|
||||
|
||||
var absolutePath = path.join(process.cwd(), nconf.get('upload_path'), path.basename(oldpicture));
|
||||
var absolutePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), path.basename(oldpicture));
|
||||
|
||||
fs.unlink(absolutePath, function (err) {
|
||||
if (err) {
|
||||
@@ -197,7 +181,7 @@ var user = require('./../user.js'),
|
||||
}
|
||||
|
||||
var filename = uid + '-profileimg' + extension;
|
||||
var uploadPath = path.join(process.cwd(), nconf.get('upload_path'), filename);
|
||||
var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename);
|
||||
|
||||
winston.info('Attempting upload to: ' + uploadPath);
|
||||
|
||||
@@ -559,6 +543,8 @@ var user = require('./../user.js'),
|
||||
else
|
||||
data.emailClass = "hide";
|
||||
|
||||
data.websiteName = data.website.replace('http://', '').replace('https://', '');
|
||||
|
||||
data.show_banned = data.banned === '1' ? '' : 'hide';
|
||||
|
||||
data.uid = uid;
|
||||
|
||||
@@ -303,7 +303,7 @@ var RDB = require('./redis.js'),
|
||||
|
||||
pids.reverse();
|
||||
async.detectSeries(pids, function(pid, next) {
|
||||
posts.getPostField(pid, 'deleted', function(deleted) {
|
||||
posts.getPostField(pid, 'deleted', function(err, deleted) {
|
||||
if (deleted === '0') next(true);
|
||||
else next(false);
|
||||
});
|
||||
|
||||
323
src/topics.js
@@ -18,30 +18,132 @@ var RDB = require('./redis.js'),
|
||||
|
||||
(function(Topics) {
|
||||
|
||||
Topics.getTopicData = function(tid, callback) {
|
||||
RDB.hgetall('topic:' + tid, function(err, data) {
|
||||
if (err === null) {
|
||||
if(data) {
|
||||
data.title = validator.sanitize(data.title).escape();
|
||||
if(data.timestamp) {
|
||||
data.relativeTime = new Date(parseInt(data.timestamp, 10)).toISOString();
|
||||
}
|
||||
Topics.post = function(uid, title, content, category_id, callback) {
|
||||
if (!category_id)
|
||||
throw new Error('Attempted to post without a category_id');
|
||||
|
||||
if (content)
|
||||
content = content.trim();
|
||||
if (title)
|
||||
title = title.trim();
|
||||
|
||||
if (!uid) {
|
||||
callback(new Error('not-logged-in'), null);
|
||||
return;
|
||||
} else if (!title || title.length < meta.config.minimumTitleLength) {
|
||||
callback(new Error('title-too-short'), null);
|
||||
return;
|
||||
} else if (!content || content.length < meta.config.miminumPostLength) {
|
||||
callback(new Error('content-too-short'), null);
|
||||
return;
|
||||
}
|
||||
|
||||
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
|
||||
if (err) lastposttime = 0;
|
||||
if (Date.now() - lastposttime < meta.config.postDelay * 1000) {
|
||||
callback(new Error('too-many-posts'), null);
|
||||
return;
|
||||
}
|
||||
|
||||
RDB.incr('next_topic_id', function(err, tid) {
|
||||
RDB.handle(err);
|
||||
|
||||
// Global Topics
|
||||
if (uid == null) uid = 0;
|
||||
if (uid !== null) {
|
||||
RDB.sadd('topics:tid', tid);
|
||||
} else {
|
||||
// need to add some unique key sent by client so we can update this with the real uid later
|
||||
RDB.lpush('topics:queued:tid', tid);
|
||||
}
|
||||
|
||||
callback(data);
|
||||
} else {
|
||||
console.log(err);
|
||||
var slug = tid + '/' + utils.slugify(title);
|
||||
var timestamp = Date.now();
|
||||
RDB.hmset('topic:' + tid, {
|
||||
'tid': tid,
|
||||
'uid': uid,
|
||||
'cid': category_id,
|
||||
'title': title,
|
||||
'slug': slug,
|
||||
'timestamp': timestamp,
|
||||
'lastposttime': 0,
|
||||
'postcount': 0,
|
||||
'viewcount': 0,
|
||||
'locked': 0,
|
||||
'deleted': 0,
|
||||
'pinned': 0
|
||||
});
|
||||
|
||||
topicSearch.index(title, tid);
|
||||
|
||||
user.addTopicIdToUser(uid, tid);
|
||||
|
||||
// let everyone know that there is an unread topic in this category
|
||||
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
|
||||
Topics.markAsRead(tid, uid);
|
||||
});
|
||||
|
||||
|
||||
// in future it may be possible to add topics to several categories, so leaving the door open here.
|
||||
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
|
||||
RDB.hincrby('category:' + category_id, 'topic_count', 1);
|
||||
RDB.incr('totaltopiccount');
|
||||
|
||||
feed.updateCategory(category_id);
|
||||
|
||||
posts.create(uid, tid, content, function(err, postData) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
} else if(!postData) {
|
||||
return callback(new Error('invalid-post'), null);
|
||||
}
|
||||
|
||||
// Auto-subscribe the post creator to the newly created topic
|
||||
threadTools.toggleFollow(tid, uid);
|
||||
|
||||
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
|
||||
callback(null, {
|
||||
topicData: topicData,
|
||||
postData: postData
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Topics.getTopicData = function(tid, callback) {
|
||||
RDB.hgetall('topic:' + tid, function(err, data) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
if(data) {
|
||||
data.title = validator.sanitize(data.title).escape();
|
||||
if(data.timestamp) {
|
||||
data.relativeTime = new Date(parseInt(data.timestamp, 10)).toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
|
||||
Topics.getTopicDataWithUser = function(tid, callback) {
|
||||
Topics.getTopicData(tid, function(topic) {
|
||||
Topics.getTopicData(tid, function(err, topic) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
user.getUserFields(topic.uid, ['username', 'userslug', 'picture'] , function(err, userData) {
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
topic.username = userData.username;
|
||||
topic.userslug = userData.userslug
|
||||
topic.picture = userData.picture;
|
||||
callback(topic);
|
||||
callback(null, topic);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -119,26 +221,24 @@ var RDB = require('./redis.js'),
|
||||
var args = ['topics:recent', '+inf', timestamp - since, 'LIMIT', start, end - start + 1];
|
||||
|
||||
RDB.zrevrangebyscore(args, function(err, tids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var latestTopics = {
|
||||
'category_name': 'Recent',
|
||||
'show_sidebar': 'hidden',
|
||||
'show_topic_button': 'hidden',
|
||||
'no_topics_message': 'hidden',
|
||||
'topic_row_size': 'col-md-12',
|
||||
'category_id': false,
|
||||
'topics': []
|
||||
};
|
||||
|
||||
if (!tids || !tids.length) {
|
||||
latestTopics.no_topics_message = 'show';
|
||||
callback(latestTopics);
|
||||
callback(err, latestTopics);
|
||||
return;
|
||||
}
|
||||
|
||||
Topics.getTopicsByTids(tids, current_user, function(topicData) {
|
||||
latestTopics.topics = topicData;
|
||||
callback(latestTopics);
|
||||
callback(err, latestTopics);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -331,7 +431,7 @@ var RDB = require('./redis.js'),
|
||||
}
|
||||
|
||||
function loadTopic(tid, callback) {
|
||||
Topics.getTopicData(tid, function(topicData) {
|
||||
Topics.getTopicData(tid, function(err, topicData) {
|
||||
if (!topicData) {
|
||||
return callback(null);
|
||||
}
|
||||
@@ -342,6 +442,7 @@ var RDB = require('./redis.js'),
|
||||
topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none';
|
||||
topicData['deleted-class'] = topicData.deleted === '1' ? 'deleted' : '';
|
||||
|
||||
topicData.unreplied = topicData.postcount === '1';
|
||||
topicData.username = topicInfo.username;
|
||||
topicData.userslug = topicInfo.userslug;
|
||||
topicData.picture = topicInfo.picture;
|
||||
@@ -381,9 +482,7 @@ var RDB = require('./redis.js'),
|
||||
Topics.increaseViewCount(tid);
|
||||
|
||||
function getTopicData(next) {
|
||||
Topics.getTopicData(tid, function(topicData) {
|
||||
next(null, topicData);
|
||||
});
|
||||
Topics.getTopicData(tid, next);
|
||||
}
|
||||
|
||||
function getTopicPosts(next) {
|
||||
@@ -426,6 +525,7 @@ var RDB = require('./redis.js'),
|
||||
'slug': topicData.slug,
|
||||
'postcount': topicData.postcount,
|
||||
'viewcount': topicData.viewcount,
|
||||
'unreplied': topicData.postcount > 1,
|
||||
'topic_id': tid,
|
||||
'expose_tools': privileges.editable ? 1 : 0,
|
||||
'posts': topicPosts,
|
||||
@@ -442,9 +542,7 @@ var RDB = require('./redis.js'),
|
||||
Topics.getTopicForCategoryView = function(tid, uid, callback) {
|
||||
|
||||
function getTopicData(next) {
|
||||
Topics.getTopicDataWithUser(tid, function(topic) {
|
||||
next(null, topic);
|
||||
});
|
||||
Topics.getTopicDataWithUser(tid, next);
|
||||
}
|
||||
|
||||
function getReadStatus(next) {
|
||||
@@ -473,6 +571,9 @@ var RDB = require('./redis.js'),
|
||||
hasRead = results[1],
|
||||
teaser = results[2];
|
||||
|
||||
topicData['pin-icon'] = topicData.pinned === '1' ? 'icon-pushpin' : 'none';
|
||||
topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none';
|
||||
|
||||
topicData.badgeclass = hasRead ? '' : 'badge-important';
|
||||
topicData.teaser_text = teaser.text || '';
|
||||
topicData.teaser_username = teaser.username || '';
|
||||
@@ -517,7 +618,7 @@ var RDB = require('./redis.js'),
|
||||
});
|
||||
|
||||
async.each(tids, function(tid, next) {
|
||||
Topics.getTopicDataWithUser(tid, function(topicData) {
|
||||
Topics.getTopicDataWithUser(tid, function(err, topicData) {
|
||||
topics.push(topicData);
|
||||
next();
|
||||
});
|
||||
@@ -546,15 +647,15 @@ var RDB = require('./redis.js'),
|
||||
}
|
||||
|
||||
Topics.getTitleByPid = function(pid, callback) {
|
||||
posts.getPostField(pid, 'tid', function(tid) {
|
||||
posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
Topics.getTopicField(tid, 'title', function(err, title) {
|
||||
callback(title);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Topics.markUnRead = function(tid) {
|
||||
RDB.del('tid:' + tid + ':read_by_uid');
|
||||
Topics.markUnRead = function(tid, callback) {
|
||||
RDB.del('tid:' + tid + ':read_by_uid', callback);
|
||||
}
|
||||
|
||||
Topics.markAsRead = function(tid, uid) {
|
||||
@@ -620,36 +721,44 @@ var RDB = require('./redis.js'),
|
||||
|
||||
Topics.getTeaser = function(tid, callback) {
|
||||
threadTools.getLatestUndeletedPid(tid, function(err, pid) {
|
||||
if (!err) {
|
||||
posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp'], function(postData) {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) {
|
||||
if (err)
|
||||
return callback(err, null);
|
||||
posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp'], function(err, postData) {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
} else if(!postData) {
|
||||
return callback(new Error('no-teaser-found'));
|
||||
}
|
||||
|
||||
var stripped = postData.content,
|
||||
timestamp = postData.timestamp,
|
||||
returnObj = {
|
||||
"pid": postData.pid,
|
||||
"username": userData.username,
|
||||
"userslug": userData.userslug,
|
||||
"picture": userData.picture,
|
||||
"timestamp": timestamp
|
||||
};
|
||||
user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
if (postData.content) {
|
||||
stripped = postData.content.replace(/>.+\n\n/, '');
|
||||
postTools.parse(stripped, function(err, stripped) {
|
||||
returnObj.text = utils.strip_tags(stripped);
|
||||
callback(null, returnObj);
|
||||
});
|
||||
} else {
|
||||
returnObj.text = '';
|
||||
var stripped = postData.content,
|
||||
timestamp = postData.timestamp,
|
||||
returnObj = {
|
||||
"pid": postData.pid,
|
||||
"username": userData.username,
|
||||
"userslug": userData.userslug,
|
||||
"picture": userData.picture,
|
||||
"timestamp": timestamp
|
||||
};
|
||||
|
||||
if (postData.content) {
|
||||
stripped = postData.content.replace(/>.+\n\n/, '');
|
||||
postTools.parse(stripped, function(err, stripped) {
|
||||
returnObj.text = utils.strip_tags(stripped);
|
||||
callback(null, returnObj);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
returnObj.text = '';
|
||||
callback(null, returnObj);
|
||||
}
|
||||
});
|
||||
} else callback(new Error('no-teaser-found'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -663,101 +772,6 @@ var RDB = require('./redis.js'),
|
||||
});
|
||||
}
|
||||
|
||||
Topics.post = function(uid, title, content, category_id, callback) {
|
||||
if (!category_id)
|
||||
throw new Error('Attempted to post without a category_id');
|
||||
|
||||
if (content)
|
||||
content = content.trim();
|
||||
if (title)
|
||||
title = title.trim();
|
||||
|
||||
if (uid === 0) {
|
||||
callback(new Error('not-logged-in'), null);
|
||||
return;
|
||||
} else if (!title || title.length < meta.config.minimumTitleLength) {
|
||||
callback(new Error('title-too-short'), null);
|
||||
return;
|
||||
} else if (!content || content.length < meta.config.miminumPostLength) {
|
||||
callback(new Error('content-too-short'), null);
|
||||
return;
|
||||
}
|
||||
|
||||
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
|
||||
if (err) lastposttime = 0;
|
||||
if (Date.now() - lastposttime < meta.config.postDelay * 1000) {
|
||||
callback(new Error('too-many-posts'), null);
|
||||
return;
|
||||
}
|
||||
|
||||
RDB.incr('next_topic_id', function(err, tid) {
|
||||
RDB.handle(err);
|
||||
|
||||
// Global Topics
|
||||
if (uid == null) uid = 0;
|
||||
if (uid !== null) {
|
||||
RDB.sadd('topics:tid', tid);
|
||||
} else {
|
||||
// need to add some unique key sent by client so we can update this with the real uid later
|
||||
RDB.lpush('topics:queued:tid', tid);
|
||||
}
|
||||
|
||||
var slug = tid + '/' + utils.slugify(title);
|
||||
var timestamp = Date.now();
|
||||
RDB.hmset('topic:' + tid, {
|
||||
'tid': tid,
|
||||
'uid': uid,
|
||||
'cid': category_id,
|
||||
'title': title,
|
||||
'slug': slug,
|
||||
'timestamp': timestamp,
|
||||
'lastposttime': 0,
|
||||
'postcount': 0,
|
||||
'viewcount': 0,
|
||||
'locked': 0,
|
||||
'deleted': 0,
|
||||
'pinned': 0
|
||||
});
|
||||
|
||||
topicSearch.index(title, tid);
|
||||
|
||||
user.addTopicIdToUser(uid, tid);
|
||||
|
||||
// let everyone know that there is an unread topic in this category
|
||||
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
|
||||
Topics.markAsRead(tid, uid);
|
||||
});
|
||||
|
||||
|
||||
// in future it may be possible to add topics to several categories, so leaving the door open here.
|
||||
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
|
||||
RDB.hincrby('category:' + category_id, 'topic_count', 1);
|
||||
RDB.incr('totaltopiccount');
|
||||
|
||||
feed.updateCategory(category_id);
|
||||
|
||||
posts.create(uid, tid, content, function(postData) {
|
||||
if (postData) {
|
||||
|
||||
// Auto-subscribe the post creator to the newly created topic
|
||||
threadTools.toggleFollow(tid, uid);
|
||||
|
||||
// Notify any users looking at the category that a new topic has arrived
|
||||
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
|
||||
io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
|
||||
io.sockets.in('recent_posts').emit('event:new_topic', topicData);
|
||||
io.sockets.in('user/' + uid).emit('event:new_post', {
|
||||
posts: postData
|
||||
});
|
||||
});
|
||||
|
||||
callback(null, postData);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Topics.getTopicField = function(tid, field, callback) {
|
||||
RDB.hget('topic:' + tid, field, callback);
|
||||
}
|
||||
@@ -780,7 +794,10 @@ var RDB = require('./redis.js'),
|
||||
|
||||
Topics.isLocked = function(tid, callback) {
|
||||
Topics.getTopicField(tid, 'locked', function(err, locked) {
|
||||
callback(locked);
|
||||
if(err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
callback(null, locked === "1");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -802,7 +819,7 @@ var RDB = require('./redis.js'),
|
||||
Topics.getPids(tid, function(err, pids) {
|
||||
|
||||
function getUid(pid, next) {
|
||||
posts.getPostField(pid, 'uid', function(uid) {
|
||||
posts.getPostField(pid, 'uid', function(err, uid) {
|
||||
if (err)
|
||||
return next(err);
|
||||
uids[uid] = 1;
|
||||
|
||||
@@ -9,7 +9,7 @@ var RDB = require('./redis.js'),
|
||||
schemaDate, thisSchemaDate;
|
||||
|
||||
Upgrade.check = function(callback) {
|
||||
var latestSchema = new Date(2013, 10, 11).getTime();
|
||||
var latestSchema = new Date(2013, 10, 22).getTime();
|
||||
|
||||
RDB.get('schemaDate', function(err, value) {
|
||||
if (parseInt(value, 10) >= latestSchema) {
|
||||
@@ -20,7 +20,7 @@ Upgrade.check = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
Upgrade.upgrade = function() {
|
||||
Upgrade.upgrade = function(callback) {
|
||||
winston.info('Beginning Redis database schema update');
|
||||
|
||||
async.series([
|
||||
@@ -128,6 +128,52 @@ Upgrade.upgrade = function() {
|
||||
winston.info('[2013/11/11] Update to postDelay skipped.');
|
||||
next();
|
||||
}
|
||||
},
|
||||
function(next) {
|
||||
thisSchemaDate = new Date(2013, 10, 22).getTime();
|
||||
if (schemaDate < thisSchemaDate) {
|
||||
RDB.keys('category:*', function(err, categories) {
|
||||
async.each(categories, function(categoryStr, next) {
|
||||
var hex;
|
||||
RDB.hgetall(categoryStr, function(err, categoryObj) {
|
||||
switch(categoryObj.blockclass) {
|
||||
case 'category-purple':
|
||||
hex = '#ab1290';
|
||||
break;
|
||||
|
||||
case 'category-darkblue':
|
||||
hex = '#004c66';
|
||||
break;
|
||||
|
||||
case 'category-blue':
|
||||
hex = '#0059b2';
|
||||
break;
|
||||
|
||||
case 'category-darkgreen':
|
||||
hex = '#004000';
|
||||
break;
|
||||
|
||||
case 'category-orange':
|
||||
hex = '#ff7a4d';
|
||||
break;
|
||||
|
||||
default:
|
||||
hex = '#0059b2';
|
||||
break;
|
||||
}
|
||||
|
||||
RDB.hset(categoryStr, 'bgColor', hex, next);
|
||||
RDB.hdel(categoryStr, 'blockclass');
|
||||
});
|
||||
}, function() {
|
||||
winston.info('[2013/11/22] Updated Category colours.');
|
||||
next();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
winston.info('[2013/11/22] Update to Category colours skipped.');
|
||||
next();
|
||||
}
|
||||
}
|
||||
// Add new schema updates here
|
||||
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 12!!!
|
||||
@@ -136,13 +182,19 @@ Upgrade.upgrade = function() {
|
||||
RDB.set('schemaDate', thisSchemaDate, function(err) {
|
||||
if (!err) {
|
||||
winston.info('[upgrade] Redis schema update complete!');
|
||||
process.exit();
|
||||
if (callback) {
|
||||
callback(err);
|
||||
} else {
|
||||
process.exit();
|
||||
}
|
||||
} else {
|
||||
winston.error('[upgrade] Could not update NodeBB schema date!');
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
winston.error('[upgrade] Errors were encountered while updating the NodeBB schema: ' + err.message);
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
57
src/user.js
@@ -1,16 +1,20 @@
|
||||
var utils = require('./../public/src/utils.js'),
|
||||
RDB = require('./redis.js'),
|
||||
emailjs = require('emailjs'),
|
||||
meta = require('./meta.js'),
|
||||
emailjsServer = emailjs.server.connect(meta.config['email:smtp:host'] || '127.0.0.1'),
|
||||
bcrypt = require('bcrypt'),
|
||||
Groups = require('./groups'),
|
||||
notifications = require('./notifications.js'),
|
||||
topics = require('./topics.js'),
|
||||
var bcrypt = require('bcrypt'),
|
||||
async = require('async'),
|
||||
emailjs = require('emailjs'),
|
||||
nconf = require('nconf'),
|
||||
winston = require('winston'),
|
||||
userSearch = require('reds').createSearch('nodebbusersearch'),
|
||||
winston = require('winston');
|
||||
check = require('validator').check,
|
||||
sanitize = require('validator').sanitize,
|
||||
|
||||
utils = require('./../public/src/utils'),
|
||||
RDB = require('./redis'),
|
||||
meta = require('./meta'),
|
||||
emailjsServer = emailjs.server.connect(meta.config['email:smtp:host'] || '127.0.0.1'),
|
||||
Groups = require('./groups'),
|
||||
notifications = require('./notifications'),
|
||||
topics = require('./topics');
|
||||
|
||||
|
||||
(function(User) {
|
||||
'use strict';
|
||||
@@ -114,13 +118,6 @@ var utils = require('./../public/src/utils.js'),
|
||||
|
||||
userSearch.index(username, uid);
|
||||
|
||||
if (typeof io !== 'undefined') {
|
||||
io.sockets.emit('user.latest', {
|
||||
userslug: userslug,
|
||||
username: username
|
||||
});
|
||||
}
|
||||
|
||||
if (password !== undefined) {
|
||||
User.hashPassword(password, function(err, hash) {
|
||||
User.setUserField(uid, 'password', hash);
|
||||
@@ -250,6 +247,9 @@ var utils = require('./../public/src/utils.js'),
|
||||
|
||||
function updateField(field, next) {
|
||||
if (data[field] !== undefined && typeof data[field] === 'string') {
|
||||
data[field] = data[field].trim();
|
||||
data[field] = sanitize(data[field]).escape();
|
||||
|
||||
if (field === 'email') {
|
||||
var gravatarpicture = User.createGravatarURLFromEmail(data[field]);
|
||||
User.setUserField(uid, 'gravatarpicture', gravatarpicture);
|
||||
@@ -271,6 +271,10 @@ var utils = require('./../public/src/utils.js'),
|
||||
return;
|
||||
} else if (field === 'signature') {
|
||||
data[field] = utils.strip_tags(data[field]);
|
||||
} else if (field === 'website') {
|
||||
if(data[field].substr(0, 7) !== 'http://' && data[field].substr(0, 8) !== 'https://') {
|
||||
data[field] = 'http://' + data[field];
|
||||
}
|
||||
}
|
||||
|
||||
User.setUserField(uid, field, data[field]);
|
||||
@@ -642,21 +646,6 @@ var utils = require('./../public/src/utils.js'),
|
||||
});
|
||||
};
|
||||
|
||||
User.latest = function(socket) {
|
||||
RDB.zrevrange('users:joindate', 0, 0, function(err, uid) {
|
||||
RDB.handle(err);
|
||||
|
||||
User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
|
||||
if (!err && userData) {
|
||||
socket.emit('user.latest', {
|
||||
userslug: userData.userslug,
|
||||
username: userData.username
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
User.getUidByUsername = function(username, callback) {
|
||||
RDB.hget('username:uid', username, callback);
|
||||
};
|
||||
@@ -1009,7 +998,9 @@ var utils = require('./../public/src/utils.js'),
|
||||
next(null, notif_data);
|
||||
});
|
||||
}, function(err, notifs) {
|
||||
notifs = notifs.sort(function(a, b) {
|
||||
notifs = notifs.filter(function(notif) {
|
||||
return notif !== null;
|
||||
}).sort(function(a, b) {
|
||||
return parseInt(b.datetime, 10) - parseInt(a.datetime, 10);
|
||||
}).map(function(notif) {
|
||||
notif.datetimeISO = new Date(parseInt(notif.datetime, 10)).toISOString();
|
||||
|
||||
106
src/webserver.js
@@ -1,31 +1,33 @@
|
||||
var express = require('express'),
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
|
||||
express = require('express'),
|
||||
express_namespace = require('express-namespace'),
|
||||
WebServer = express(),
|
||||
server = require('http').createServer(WebServer),
|
||||
RedisStore = require('connect-redis')(express),
|
||||
path = require('path'),
|
||||
RDB = require('./redis'),
|
||||
utils = require('../public/src/utils.js'),
|
||||
pkg = require('../package.json'),
|
||||
fs = require('fs'),
|
||||
|
||||
user = require('./user.js'),
|
||||
categories = require('./categories.js'),
|
||||
posts = require('./posts.js'),
|
||||
topics = require('./topics.js'),
|
||||
notifications = require('./notifications.js'),
|
||||
admin = require('./routes/admin.js'),
|
||||
userRoute = require('./routes/user.js'),
|
||||
apiRoute = require('./routes/api.js'),
|
||||
auth = require('./routes/authentication.js'),
|
||||
meta = require('./meta.js'),
|
||||
feed = require('./feed'),
|
||||
plugins = require('./plugins'),
|
||||
nconf = require('nconf'),
|
||||
winston = require('winston'),
|
||||
validator = require('validator'),
|
||||
async = require('async'),
|
||||
logger = require('./logger.js');
|
||||
|
||||
pkg = require('../package.json'),
|
||||
|
||||
utils = require('../public/src/utils'),
|
||||
RDB = require('./redis'),
|
||||
user = require('./user'),
|
||||
categories = require('./categories'),
|
||||
posts = require('./posts'),
|
||||
topics = require('./topics'),
|
||||
notifications = require('./notifications'),
|
||||
admin = require('./routes/admin'),
|
||||
userRoute = require('./routes/user'),
|
||||
apiRoute = require('./routes/api'),
|
||||
auth = require('./routes/authentication'),
|
||||
meta = require('./meta'),
|
||||
feed = require('./feed'),
|
||||
plugins = require('./plugins'),
|
||||
logger = require('./logger');
|
||||
|
||||
(function (app) {
|
||||
"use strict";
|
||||
@@ -33,17 +35,21 @@ var express = require('express'),
|
||||
var templates = null,
|
||||
clientScripts;
|
||||
|
||||
// Minify client-side libraries
|
||||
meta.js.get(function (err, scripts) {
|
||||
clientScripts = scripts.map(function (script) {
|
||||
script = {
|
||||
script: script
|
||||
};
|
||||
|
||||
plugins.ready(function() {
|
||||
// Minify client-side libraries
|
||||
meta.js.get(function (err, scripts) {
|
||||
clientScripts = scripts.map(function (script) {
|
||||
script = {
|
||||
script: script
|
||||
};
|
||||
|
||||
return script;
|
||||
return script;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.app = app;
|
||||
|
||||
/**
|
||||
@@ -144,7 +150,7 @@ var express = require('express'),
|
||||
res.locals.csrf_token = req.session._csrf;
|
||||
|
||||
// Disable framing
|
||||
res.setHeader("X-Frame-Options", "DENY");
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
|
||||
next();
|
||||
});
|
||||
@@ -306,9 +312,17 @@ var express = require('express'),
|
||||
templates = global.templates;
|
||||
|
||||
// translate all static templates served by webserver here. ex. footer, logout
|
||||
translator.translate(templates.footer.toString(), function(parsedTemplate) {
|
||||
templates.footer = parsedTemplate;
|
||||
plugins.fireHook('filter:footer.build', '', function(err, appendHTML) {
|
||||
var footer = templates.footer.parse({
|
||||
footerHTML: appendHTML
|
||||
});
|
||||
|
||||
translator.translate(footer, function(parsedTemplate) {
|
||||
templates.footer = parsedTemplate;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
translator.translate(templates.logout.toString(), function(parsedTemplate) {
|
||||
templates.logout = parsedTemplate;
|
||||
});
|
||||
@@ -639,13 +653,39 @@ var express = require('express'),
|
||||
"Sitemap: " + nconf.get('url') + "sitemap.xml");
|
||||
});
|
||||
|
||||
app.get('/recent.rss', function(req, res) {
|
||||
var rssPath = path.join(__dirname, '../', 'feeds/recent.rss'),
|
||||
loadFeed = function () {
|
||||
fs.readFile(rssPath, function (err, data) {
|
||||
if (err) {
|
||||
res.type('text').send(404, "Unable to locate an rss feed at this location.");
|
||||
} else {
|
||||
res.type('xml').set('Content-Length', data.length).send(data);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
if (!fs.existsSync(rssPath)) {
|
||||
feed.updateRecent(function (err) {
|
||||
if (err) {
|
||||
res.redirect('/404');
|
||||
} else {
|
||||
loadFeed();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
loadFeed();
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/recent/:term?', function (req, res) {
|
||||
// TODO consolidate with /recent route as well -> that can be combined into this area. See "Basic Routes" near top.
|
||||
app.build_header({
|
||||
req: req,
|
||||
res: res
|
||||
}, function (err, header) {
|
||||
res.send(header + app.create_route("recent/" + req.params.term, null, "recent") + templates.footer);
|
||||
res.send(header + app.create_route('recent/' + req.params.term, null, 'recent') + templates.footer);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -676,7 +716,7 @@ var express = require('express'),
|
||||
req: req,
|
||||
res: res
|
||||
}, function (err, header) {
|
||||
res.send(header + app.create_route("search/" + req.params.term, null, "search") + templates.footer);
|
||||
res.send(header + app.create_route('search/' + req.params.term, null, 'search') + templates.footer);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -736,4 +776,4 @@ var express = require('express'),
|
||||
}(WebServer));
|
||||
|
||||
|
||||
global.server = server;
|
||||
global.server = server;
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
|
||||
var cookie = require('cookie'),
|
||||
var cookie = require('cookie'),
|
||||
express = require('express'),
|
||||
user = require('./user.js'),
|
||||
Groups = require('./groups'),
|
||||
posts = require('./posts.js'),
|
||||
favourites = require('./favourites.js'),
|
||||
utils = require('../public/src/utils.js'),
|
||||
util = require('util'),
|
||||
topics = require('./topics.js'),
|
||||
categories = require('./categories.js'),
|
||||
notifications = require('./notifications.js'),
|
||||
threadTools = require('./threadTools.js'),
|
||||
postTools = require('./postTools.js'),
|
||||
meta = require('./meta.js'),
|
||||
async = require('async'),
|
||||
fs = require('fs'),
|
||||
nconf = require('nconf'),
|
||||
winston = require('winston'),
|
||||
|
||||
RedisStoreLib = require('connect-redis')(express),
|
||||
RDB = require('./redis'),
|
||||
util = require('util'),
|
||||
logger = require('./logger.js'),
|
||||
fs = require('fs'),
|
||||
RedisStore = new RedisStoreLib({
|
||||
client: RDB,
|
||||
ttl: 60 * 60 * 24 * 14
|
||||
}),
|
||||
nconf = require('nconf'),
|
||||
|
||||
user = require('./user'),
|
||||
Groups = require('./groups'),
|
||||
posts = require('./posts'),
|
||||
favourites = require('./favourites'),
|
||||
utils = require('../public/src/utils'),
|
||||
topics = require('./topics'),
|
||||
categories = require('./categories'),
|
||||
notifications = require('./notifications'),
|
||||
threadTools = require('./threadTools'),
|
||||
postTools = require('./postTools'),
|
||||
meta = require('./meta'),
|
||||
logger = require('./logger'),
|
||||
socketCookieParser = express.cookieParser(nconf.get('secret')),
|
||||
admin = {
|
||||
'categories': require('./admin/categories.js'),
|
||||
'user': require('./admin/user.js')
|
||||
'categories': require('./admin/categories'),
|
||||
'user': require('./admin/user')
|
||||
},
|
||||
plugins = require('./plugins'),
|
||||
winston = require('winston');
|
||||
plugins = require('./plugins');
|
||||
|
||||
|
||||
var users = {},
|
||||
@@ -43,8 +44,9 @@ module.exports.logoutUser = function(uid) {
|
||||
userSockets[uid][i].emit('event:disconnect');
|
||||
userSockets[uid][i].disconnect();
|
||||
|
||||
if(!userSockets[uid])
|
||||
if(!userSockets[uid]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,11 +171,11 @@ module.exports.init = function(io) {
|
||||
var anonymousCount = getAnonymousCount(roomName);
|
||||
|
||||
if (uids.length === 0) {
|
||||
io.sockets. in (roomName).emit('api:get_users_in_room', { users: [], anonymousCount:0 });
|
||||
io.sockets. in (roomName).emit('api:get_users_in_room', { users: [], anonymousCount: anonymousCount });
|
||||
} else {
|
||||
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], function(err, users) {
|
||||
if(!err)
|
||||
io.sockets. in (roomName).emit('api:get_users_in_room', { users: users, anonymousCount:anonymousCount });
|
||||
io.sockets. in (roomName).emit('api:get_users_in_room', { users: users, anonymousCount: anonymousCount });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -247,10 +249,6 @@ module.exports.init = function(io) {
|
||||
posts.getTopicPostStats();
|
||||
});
|
||||
|
||||
socket.on('user.latest', function(data) {
|
||||
user.latest(socket, data);
|
||||
});
|
||||
|
||||
socket.on('user.email.exists', function(data) {
|
||||
user.email.exists(socket, data.email);
|
||||
});
|
||||
@@ -368,11 +366,24 @@ module.exports.init = function(io) {
|
||||
posts.emitContentTooShortAlert(socket);
|
||||
} else if (err.message === 'too-many-posts') {
|
||||
posts.emitTooManyPostsAlert(socket);
|
||||
} else {
|
||||
socket.emit('event:alert', {
|
||||
title: 'Error',
|
||||
message: err.message,
|
||||
type: 'warning',
|
||||
timeout: 7500
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
io.sockets.in('category_' + data.category_id).emit('event:new_topic', result.topicData);
|
||||
io.sockets.in('recent_posts').emit('event:new_topic', result.topicData);
|
||||
io.sockets.in('user/' + uid).emit('event:new_post', {
|
||||
posts: result.postData
|
||||
});
|
||||
|
||||
posts.getTopicPostStats();
|
||||
|
||||
socket.emit('event:alert', {
|
||||
@@ -533,7 +544,7 @@ module.exports.init = function(io) {
|
||||
});
|
||||
|
||||
socket.on('api:posts.getRawPost', function(data) {
|
||||
posts.getPostField(data.pid, 'content', function(raw) {
|
||||
posts.getPostField(data.pid, 'content', function(err, raw) {
|
||||
socket.emit('api:posts.getRawPost', {
|
||||
post: raw
|
||||
});
|
||||
@@ -560,15 +571,33 @@ module.exports.init = function(io) {
|
||||
postTools.edit(uid, data.pid, data.title, data.content, data.images);
|
||||
});
|
||||
|
||||
socket.on('api:posts.delete', function(data) {
|
||||
postTools.delete(uid, data.pid, function() {
|
||||
socket.on('api:posts.delete', function(data, callback) {
|
||||
postTools.delete(uid, data.pid, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
posts.getTopicPostStats();
|
||||
|
||||
io.sockets.in('topic_' + data.tid).emit('event:post_deleted', {
|
||||
pid: data.pid
|
||||
});
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('api:posts.restore', function(data) {
|
||||
postTools.restore(uid, data.pid, function() {
|
||||
socket.on('api:posts.restore', function(data, callback) {
|
||||
postTools.restore(uid, data.pid, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
posts.getTopicPostStats();
|
||||
|
||||
io.sockets.in('topic_' + data.tid).emit('event:post_restored', {
|
||||
pid: data.pid
|
||||
});
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -688,7 +717,7 @@ module.exports.init = function(io) {
|
||||
socket.on('api:composer.push', function(data) {
|
||||
if (uid > 0 || meta.config.allowGuestPosting === '1') {
|
||||
if (parseInt(data.tid) > 0) {
|
||||
topics.getTopicData(data.tid, function(topicData) {
|
||||
topics.getTopicData(data.tid, function(err, topicData) {
|
||||
if (data.body)
|
||||
topicData.body = data.body;
|
||||
|
||||
@@ -714,9 +743,7 @@ module.exports.init = function(io) {
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
posts.getPostFields(data.pid, ['content'], function(raw) {
|
||||
next(null, raw);
|
||||
});
|
||||
posts.getPostFields(data.pid, ['content'], next);
|
||||
},
|
||||
function(next) {
|
||||
topics.getTitleByPid(data.pid, function(title) {
|
||||
@@ -739,7 +766,7 @@ module.exports.init = function(io) {
|
||||
});
|
||||
|
||||
socket.on('api:composer.editCheck', function(pid) {
|
||||
posts.getPostField(pid, 'tid', function(tid) {
|
||||
posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
postTools.isMain(pid, tid, function(isMain) {
|
||||
socket.emit('api:composer.editCheck', {
|
||||
titleEditable: isMain
|
||||
@@ -800,8 +827,12 @@ module.exports.init = function(io) {
|
||||
var start = data.after,
|
||||
end = start + 9;
|
||||
|
||||
topics.getLatestTopics(uid, start, end, data.term, function(latestTopics) {
|
||||
callback(latestTopics);
|
||||
topics.getLatestTopics(uid, start, end, data.term, function(err, latestTopics) {
|
||||
if (!err) {
|
||||
callback(latestTopics);
|
||||
} else {
|
||||
winston.error('[socket api:topics.loadMoreRecentTopics] ' + err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
85
tests/topics.js
Normal file
@@ -0,0 +1,85 @@
|
||||
var winston = require('winston');
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
winston.error('Encountered error while running test suite: ' + err.message);
|
||||
});
|
||||
|
||||
var assert = require('assert'),
|
||||
RDB = require('../mocks/redismock');
|
||||
|
||||
// Reds is not technically used in this test suite, but its invocation is required to stop the included
|
||||
// libraries from trying to connect to the default Redis host/port
|
||||
var reds = require('reds');
|
||||
reds.createClient = function () {
|
||||
return reds.client || (reds.client = RDB);
|
||||
};
|
||||
|
||||
var Topics = require('../src/topics');
|
||||
|
||||
describe('Topic\'s', function() {
|
||||
var topic;
|
||||
|
||||
beforeEach(function(){
|
||||
topic = {
|
||||
userId: 1,
|
||||
categoryId: 1,
|
||||
title: 'Test Topic Title',
|
||||
content: 'The content of test topic'
|
||||
};
|
||||
});
|
||||
|
||||
describe('.post', function() {
|
||||
|
||||
it('should create a new topic with proper parameters', function(done) {
|
||||
Topics.post(topic.userId, topic.title, topic.content, topic.categoryId, function(err, result) {
|
||||
assert.equal(err, null, 'was created with error');
|
||||
assert.ok(result);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to create new topic with wrong parameters', function(done) {
|
||||
topic.userId = null;
|
||||
|
||||
Topics.post(topic.userId, topic.title, topic.content, topic.categoryId, function(err, result) {
|
||||
assert.equal(err.message, 'not-logged-in');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get methods', function() {
|
||||
var newTopic;
|
||||
var newPost;
|
||||
|
||||
beforeEach(function(done){
|
||||
Topics.post(topic.userId, topic.title, topic.content, topic.categoryId, function(err, result) {
|
||||
newTopic = result.topicData;
|
||||
newPost = result.postData;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getTopicData', function() {
|
||||
it('should not receive errors', function(done) {
|
||||
Topics.getTopicData(newTopic.tid, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getTopicDataWithUser', function() {
|
||||
it('should not receive errors', function(done) {
|
||||
Topics.getTopicDataWithUser(newTopic.tid, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function() {
|
||||
RDB.send_command('flushdb', [], function(error){
|
||||
if(error){
|
||||
winston.error(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||