doing some reshuffling in public/src
public/src/admin and public/src/client; also some cleanup in src/meta/js, no need to filter out admin files anymorev1.18.x
parent
006322f386
commit
b51c90dcb3
@ -0,0 +1,33 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, app, socket */
|
||||||
|
|
||||||
|
define('forum/admin/appearance/customise', ['forum/admin/settings'], function(Settings) {
|
||||||
|
var Customise = {};
|
||||||
|
|
||||||
|
Customise.init = function() {
|
||||||
|
Settings.prepare(function() {
|
||||||
|
$('#customCSS').text($('#customCSS-holder').val());
|
||||||
|
$('#customHTML').text($('#customHTML-holder').val());
|
||||||
|
|
||||||
|
var customCSS = ace.edit("customCSS"),
|
||||||
|
customHTML = ace.edit("customHTML");
|
||||||
|
|
||||||
|
customCSS.setTheme("ace/theme/twilight");
|
||||||
|
customCSS.getSession().setMode("ace/mode/css");
|
||||||
|
|
||||||
|
customCSS.on('change', function(e) {
|
||||||
|
$('#customCSS-holder').val(customCSS.getValue());
|
||||||
|
});
|
||||||
|
|
||||||
|
customHTML.setTheme("ace/theme/twilight");
|
||||||
|
customHTML.getSession().setMode("ace/mode/html");
|
||||||
|
|
||||||
|
customHTML.on('change', function(e) {
|
||||||
|
$('#customHTML-holder').val(customHTML.getValue());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Customise;
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,73 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, app, socket */
|
||||||
|
|
||||||
|
define('forum/admin/appearance/skins', function() {
|
||||||
|
var Skins = {};
|
||||||
|
|
||||||
|
Skins.init = function() {
|
||||||
|
var scriptEl = $('<script />');
|
||||||
|
scriptEl.attr('src', '//bootswatch.aws.af.cm/3/?callback=bootswatchListener');
|
||||||
|
$('body').append(scriptEl);
|
||||||
|
|
||||||
|
$('#bootstrap_themes').on('click', function(e){
|
||||||
|
var target = $(e.target),
|
||||||
|
action = target.attr('data-action');
|
||||||
|
|
||||||
|
if (action && action === 'use') {
|
||||||
|
var parentEl = target.parents('li'),
|
||||||
|
themeType = parentEl.attr('data-type'),
|
||||||
|
cssSrc = parentEl.attr('data-css'),
|
||||||
|
themeId = parentEl.attr('data-theme');
|
||||||
|
|
||||||
|
socket.emit('admin.themes.set', {
|
||||||
|
type: themeType,
|
||||||
|
id: themeId,
|
||||||
|
src: cssSrc
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
highlightSelectedTheme(themeId);
|
||||||
|
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'admin:theme',
|
||||||
|
type: 'info',
|
||||||
|
title: 'Theme Changed',
|
||||||
|
message: 'Please restart your NodeBB to fully activate this theme',
|
||||||
|
timeout: 5000,
|
||||||
|
clickfn: function() {
|
||||||
|
socket.emit('admin.restart');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Skins.render = function(bootswatch) {
|
||||||
|
var themeContainer = $('#bootstrap_themes');
|
||||||
|
|
||||||
|
templates.parse('admin/partials/theme_list', {
|
||||||
|
themes: bootswatch.themes.map(function(theme) {
|
||||||
|
return {
|
||||||
|
type: 'bootswatch',
|
||||||
|
id: theme.name,
|
||||||
|
name: theme.name,
|
||||||
|
description: theme.description,
|
||||||
|
screenshot_url: theme.thumbnail,
|
||||||
|
url: theme.preview,
|
||||||
|
css: theme.cssCdn
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}, function(html) {
|
||||||
|
themeContainer.html(html);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function highlightSelectedTheme(themeId) {
|
||||||
|
$('.themes li[data-theme]').removeClass('btn-warning');
|
||||||
|
$('.themes li[data-theme="' + themeId + '"]').addClass('btn-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Skins;
|
||||||
|
});
|
@ -0,0 +1,93 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, app, socket */
|
||||||
|
|
||||||
|
define('forum/admin/appearance/themes', function() {
|
||||||
|
var Themes = {};
|
||||||
|
|
||||||
|
Themes.init = function() {
|
||||||
|
$('#installed_themes').on('click', function(e){
|
||||||
|
var target = $(e.target),
|
||||||
|
action = target.attr('data-action');
|
||||||
|
|
||||||
|
if (action && action === 'use') {
|
||||||
|
var parentEl = target.parents('li'),
|
||||||
|
themeType = parentEl.attr('data-type'),
|
||||||
|
cssSrc = parentEl.attr('data-css'),
|
||||||
|
themeId = parentEl.attr('data-theme');
|
||||||
|
|
||||||
|
socket.emit('admin.themes.set', {
|
||||||
|
type: themeType,
|
||||||
|
id: themeId,
|
||||||
|
src: cssSrc
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
highlightSelectedTheme(themeId);
|
||||||
|
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'admin:theme',
|
||||||
|
type: 'info',
|
||||||
|
title: 'Theme Changed',
|
||||||
|
message: 'Please restart your NodeBB to fully activate this theme',
|
||||||
|
timeout: 5000,
|
||||||
|
clickfn: function() {
|
||||||
|
socket.emit('admin.restart');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#revert_theme').on('click', function() {
|
||||||
|
bootbox.confirm('Are you sure you wish to remove the custom theme and restore the NodeBB default theme?', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('admin.themes.set', {
|
||||||
|
type: 'local',
|
||||||
|
id: 'nodebb-theme-vanilla'
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
highlightSelectedTheme('nodebb-theme-vanilla');
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'admin:theme',
|
||||||
|
type: 'success',
|
||||||
|
title: 'Theme Changed',
|
||||||
|
message: 'You have successfully reverted your NodeBB back to it\'s default theme.',
|
||||||
|
timeout: 3500
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Installed Themes
|
||||||
|
socket.emit('admin.themes.getInstalled', function(err, themes) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var instListEl = $('#installed_themes');
|
||||||
|
|
||||||
|
if (!themes.length) {
|
||||||
|
instListEl.append($('<li/ >').addClass('no-themes').html('No installed themes found'));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
templates.parse('admin/partials/theme_list', {
|
||||||
|
themes: themes
|
||||||
|
}, function(html) {
|
||||||
|
instListEl.html(html);
|
||||||
|
highlightSelectedTheme(config['theme:id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function highlightSelectedTheme(themeId) {
|
||||||
|
$('.themes li[data-theme]').removeClass('btn-warning');
|
||||||
|
$('.themes li[data-theme="' + themeId + '"]').addClass('btn-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Themes;
|
||||||
|
});
|
@ -0,0 +1,83 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, app, socket */
|
||||||
|
|
||||||
|
define('forum/admin/extend/plugins', function() {
|
||||||
|
var Plugins = {
|
||||||
|
init: function() {
|
||||||
|
var pluginsList = $('.plugins'),
|
||||||
|
numPlugins = pluginsList[0].querySelectorAll('li').length,
|
||||||
|
pluginID;
|
||||||
|
|
||||||
|
if (numPlugins > 0) {
|
||||||
|
|
||||||
|
pluginsList.on('click', 'button[data-action="toggleActive"]', function() {
|
||||||
|
pluginID = $(this).parents('li').attr('data-plugin-id');
|
||||||
|
var btn = $(this);
|
||||||
|
socket.emit('admin.plugins.toggleActive', pluginID, function(err, status) {
|
||||||
|
btn.html('<i class="fa fa-power-off"></i> ' + (status.active ? 'Deactivate' : 'Activate'));
|
||||||
|
btn.toggleClass('btn-warning', status.active).toggleClass('btn-success', !status.active);
|
||||||
|
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'plugin_toggled',
|
||||||
|
title: 'Plugin ' + (status.active ? 'Enabled' : 'Disabled'),
|
||||||
|
message: status.active ? 'Please restart your NodeBB to fully activate this plugin' : 'Plugin successfully deactivated',
|
||||||
|
type: status.active ? 'warning' : 'success',
|
||||||
|
timeout: 5000,
|
||||||
|
clickfn: function() {
|
||||||
|
socket.emit('admin.restart');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pluginsList.on('click', 'button[data-action="toggleInstall"]', function() {
|
||||||
|
pluginID = $(this).parents('li').attr('data-plugin-id');
|
||||||
|
|
||||||
|
var btn = $(this);
|
||||||
|
var activateBtn = btn.siblings('[data-action="toggleActive"]');
|
||||||
|
btn.html(btn.html() + 'ing')
|
||||||
|
.attr('disabled', true)
|
||||||
|
.find('i').attr('class', 'fa fa-refresh fa-spin');
|
||||||
|
|
||||||
|
socket.emit('admin.plugins.toggleInstall', pluginID, function(err, status) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.installed) {
|
||||||
|
btn.html('<i class="fa fa-trash-o"></i> Uninstall');
|
||||||
|
} else {
|
||||||
|
btn.html('<i class="fa fa-download"></i> Install');
|
||||||
|
|
||||||
|
}
|
||||||
|
activateBtn.toggleClass('hidden', !status.installed);
|
||||||
|
|
||||||
|
btn.toggleClass('btn-danger', status.installed).toggleClass('btn-success', !status.installed)
|
||||||
|
.attr('disabled', false);
|
||||||
|
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'plugin_toggled',
|
||||||
|
title: 'Plugin ' + (status.installed ? 'Installed' : 'Uninstalled'),
|
||||||
|
message: status.installed ? 'Plugin successfully installed, please activate the plugin.' : 'The plugin has been successfully deactivated and uninstalled.',
|
||||||
|
type: 'info',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#plugin-search').on('input propertychange', function() {
|
||||||
|
var term = $(this).val();
|
||||||
|
$('.plugins li').each(function() {
|
||||||
|
var pluginId = $(this).attr('data-plugin-id');
|
||||||
|
$(this).toggleClass('hide', pluginId && pluginId.indexOf(term) === -1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pluginsList.append('<li><p><i>No plugins found.</i></p></li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Plugins;
|
||||||
|
});
|
@ -0,0 +1,198 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, app, socket */
|
||||||
|
|
||||||
|
define('forum/admin/extend/widgets', function() {
|
||||||
|
var Widgets = {};
|
||||||
|
|
||||||
|
Widgets.init = function() {
|
||||||
|
prepareWidgets();
|
||||||
|
|
||||||
|
$('#widgets .nav-pills a').on('click', function(ev) {
|
||||||
|
var $this = $(this);
|
||||||
|
$('#widgets .nav-pills li').removeClass('active');
|
||||||
|
$this.parent().addClass('active');
|
||||||
|
|
||||||
|
$('#widgets .tab-pane').removeClass('active');
|
||||||
|
$('#widgets .tab-pane[data-template="' + $this.attr('data-template') + '"]').addClass('active');
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function prepareWidgets() {
|
||||||
|
$('[data-location="drafts"]').insertAfter($('[data-location="drafts"]').closest('.tab-content'));
|
||||||
|
|
||||||
|
$('#widgets .available-widgets .widget-panel').draggable({
|
||||||
|
helper: function(e) {
|
||||||
|
return $(e.target).parents('.widget-panel').clone().addClass('block').width($(e.target.parentNode).width());
|
||||||
|
},
|
||||||
|
distance: 10,
|
||||||
|
connectToSortable: ".widget-area"
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#widgets .available-containers .containers > [data-container-html]').draggable({
|
||||||
|
helper: function(e) {
|
||||||
|
var target = $(e.target);
|
||||||
|
target = target.attr('data-container-html') ? target : target.parents('[data-container-html]');
|
||||||
|
|
||||||
|
return target.clone().addClass('block').width(target.width()).css('opacity', '0.5');
|
||||||
|
},
|
||||||
|
distance: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
function appendToggle(el) {
|
||||||
|
if (!el.hasClass('block')) {
|
||||||
|
el.addClass('block')
|
||||||
|
.droppable({
|
||||||
|
accept: '[data-container-html]',
|
||||||
|
drop: function(event, ui) {
|
||||||
|
var el = $(this);
|
||||||
|
|
||||||
|
el.find('.panel-body .container-html').val(ui.draggable.attr('data-container-html'));
|
||||||
|
el.find('.panel-body').removeClass('hidden');
|
||||||
|
},
|
||||||
|
hoverClass: "panel-info"
|
||||||
|
})
|
||||||
|
.children('.panel-heading')
|
||||||
|
.append('<div class="pull-right pointer"><span class="delete-widget"><i class="fa fa-times-circle"></i></span></div><div class="pull-left pointer"><span class="toggle-widget"><i class="fa fa-chevron-circle-down"></i></span> </div>')
|
||||||
|
.children('small').html('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#widgets .widget-area').sortable({
|
||||||
|
update: function (event, ui) {
|
||||||
|
appendToggle(ui.item);
|
||||||
|
},
|
||||||
|
connectWith: "div"
|
||||||
|
}).on('click', '.toggle-widget', function() {
|
||||||
|
$(this).parents('.widget-panel').children('.panel-body').toggleClass('hidden');
|
||||||
|
}).on('click', '.delete-widget', function() {
|
||||||
|
var panel = $(this).parents('.widget-panel');
|
||||||
|
|
||||||
|
bootbox.confirm('Are you sure you wish to delete this widget?', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
panel.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('dblclick', '.panel-heading', function() {
|
||||||
|
$(this).parents('.widget-panel').children('.panel-body').toggleClass('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#widgets .save').on('click', saveWidgets);
|
||||||
|
|
||||||
|
function saveWidgets() {
|
||||||
|
var total = $('#widgets [data-template][data-location]').length;
|
||||||
|
|
||||||
|
$('#widgets [data-template][data-location]').each(function(i, el) {
|
||||||
|
el = $(el);
|
||||||
|
|
||||||
|
var template = el.attr('data-template'),
|
||||||
|
location = el.attr('data-location'),
|
||||||
|
area = el.children('.widget-area'),
|
||||||
|
widgets = [];
|
||||||
|
|
||||||
|
area.find('.widget-panel[data-widget]').each(function() {
|
||||||
|
var widgetData = {},
|
||||||
|
data = $(this).find('form').serializeArray();
|
||||||
|
|
||||||
|
for (var d in data) {
|
||||||
|
if (data.hasOwnProperty(d)) {
|
||||||
|
if (data[d].name) {
|
||||||
|
widgetData[data[d].name] = data[d].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets.push({
|
||||||
|
widget: $(this).attr('data-widget'),
|
||||||
|
data: widgetData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('admin.widgets.set', {
|
||||||
|
template: template,
|
||||||
|
location: location,
|
||||||
|
widgets: widgets
|
||||||
|
}, function(err) {
|
||||||
|
total--;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total === 0) {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'admin:widgets',
|
||||||
|
type: 'success',
|
||||||
|
title: 'Widgets Updated',
|
||||||
|
message: 'Successfully updated widgets',
|
||||||
|
timeout: 2500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateWidget(widget, data) {
|
||||||
|
if (data.title) {
|
||||||
|
var title = widget.find('.panel-heading strong');
|
||||||
|
title.text(title.text() + ' - ' + data.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.find('input, textarea').each(function() {
|
||||||
|
var input = $(this),
|
||||||
|
value = data[input.attr('name')];
|
||||||
|
|
||||||
|
if (this.type === 'checkbox') {
|
||||||
|
input.attr('checked', !!value);
|
||||||
|
} else {
|
||||||
|
input.val(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.get(RELATIVE_PATH + '/api/admin/extend/widgets', function(data) {
|
||||||
|
var areas = data.areas;
|
||||||
|
|
||||||
|
for(var i=0; i<areas.length; ++i) {
|
||||||
|
var area = areas[i],
|
||||||
|
widgetArea = $('#widgets .area[data-template="' + area.template + '"][data-location="' + area.location + '"]').find('.widget-area');
|
||||||
|
|
||||||
|
widgetArea.html('');
|
||||||
|
|
||||||
|
for (var k=0; k<area.data.length; ++k) {
|
||||||
|
var widgetData = area.data[k],
|
||||||
|
widgetEl = $('.available-widgets [data-widget="' + widgetData.widget + '"]').clone(true);
|
||||||
|
|
||||||
|
widgetArea.append(populateWidget(widgetEl, widgetData.data));
|
||||||
|
appendToggle(widgetEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.color-selector').on('click', '.btn', function() {
|
||||||
|
var btn = $(this),
|
||||||
|
selector = btn.parents('.color-selector'),
|
||||||
|
container = selector.parents('[data-container-html]'),
|
||||||
|
classList = [];
|
||||||
|
|
||||||
|
selector.children().each(function() {
|
||||||
|
classList.push($(this).attr('data-class'));
|
||||||
|
});
|
||||||
|
|
||||||
|
container
|
||||||
|
.removeClass(classList.join(' '))
|
||||||
|
.addClass(btn.attr('data-class'));
|
||||||
|
|
||||||
|
container.attr('data-container-html', container.attr('data-container-html')
|
||||||
|
.replace(/class="[a-zA-Z0-9-\s]+"/, 'class="' + container[0].className.replace(' pointer ui-draggable', '') + '"')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Widgets;
|
||||||
|
});
|
@ -0,0 +1,190 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define, app, socket, Hammer, RELATIVE_PATH */
|
||||||
|
|
||||||
|
define('forum/admin/footer', ['forum/admin/settings'], function(Settings) {
|
||||||
|
var acpIndex;
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
if(!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||||
|
getSearchIndex();
|
||||||
|
} else {
|
||||||
|
activateMobile();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.end', function(ev, data) {
|
||||||
|
var url = data.url;
|
||||||
|
|
||||||
|
selectMenuItem(data.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
setupMainMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
function activateMobile() {
|
||||||
|
$('.admin').addClass('mobile');
|
||||||
|
$('#main-menu').addClass('transitioning');
|
||||||
|
|
||||||
|
Hammer(document.body).on('swiperight', function(e) {
|
||||||
|
$('#main-menu').addClass('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
Hammer(document.body).on('swipeleft', function(e) {
|
||||||
|
$('#main-menu').removeClass('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
Hammer($('#main-menu')[0]).on('swiperight', function(e) {
|
||||||
|
$('#main-menu').addClass('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
Hammer($('#main-menu')[0]).on('swipeleft', function(e) {
|
||||||
|
$('#main-menu').removeClass('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('scroll', function() {
|
||||||
|
$('#main-menu').height($(window).height() + 20);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupMainMenu() {
|
||||||
|
$('.sidebar-nav .nav-header').on('click', function() {
|
||||||
|
$(this).parents('.sidebar-nav').toggleClass('open');
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.nano').nanoScroller();
|
||||||
|
}, 500); // replace with animationend event
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.nano').nanoScroller();
|
||||||
|
|
||||||
|
$('#main-menu .nav-list > li a').append('<span class="pull-right"><i class="fa fa-inverse fa-arrow-circle-right"></i> </span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectMenuItem(url) {
|
||||||
|
$('#main-menu .nav-list > li').removeClass('active').each(function() {
|
||||||
|
var menu = $(this),
|
||||||
|
category = menu.parents('.sidebar-nav'),
|
||||||
|
href = menu.children('a').attr('href');
|
||||||
|
|
||||||
|
if (href && href.slice(1).indexOf(url) !== -1) {
|
||||||
|
category.addClass('open');
|
||||||
|
menu.addClass('active');
|
||||||
|
modifyBreadcrumb(category.find('.nav-header').text(), menu.text());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function modifyBreadcrumb() {
|
||||||
|
var caret = ' <i class="fa fa-angle-right"></i> ';
|
||||||
|
|
||||||
|
$('#breadcrumbs').html(caret + Array.prototype.slice.call(arguments).join(caret));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchIndex() {
|
||||||
|
$.getJSON(RELATIVE_PATH + '/templates/indexed.json', function (data) {
|
||||||
|
acpIndex = data;
|
||||||
|
for (var file in acpIndex) {
|
||||||
|
if (acpIndex.hasOwnProperty(file)) {
|
||||||
|
acpIndex[file] = acpIndex[file].replace(/<img/g, '<none'); // can't think of a better solution, see #2153
|
||||||
|
acpIndex[file] = $('<div class="search-container">' + acpIndex[file] + '</div>');
|
||||||
|
acpIndex[file].find('script').remove();
|
||||||
|
|
||||||
|
acpIndex[file] = acpIndex[file].text().toLowerCase().replace(/[ |\r|\n]+/g, ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete acpIndex['/admin/header.tpl'];
|
||||||
|
delete acpIndex['/admin/footer.tpl'];
|
||||||
|
|
||||||
|
setupACPSearch();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupACPSearch() {
|
||||||
|
var menu = $('#acp-search .dropdown-menu'),
|
||||||
|
routes = [],
|
||||||
|
input = $('#acp-search input'),
|
||||||
|
firstResult = null;
|
||||||
|
|
||||||
|
input.on('keyup', function() {
|
||||||
|
$('#acp-search .dropdown').addClass('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#acp-search').parents('form').on('submit', function(ev) {
|
||||||
|
var input = $(this).find('input'),
|
||||||
|
href = firstResult ? firstResult : RELATIVE_PATH + '/search/' + input.val();
|
||||||
|
|
||||||
|
ajaxify.go(href.replace(/^\//, ''));
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#acp-search .dropdown').removeClass('open');
|
||||||
|
$(input).blur();
|
||||||
|
}, 150);
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.sidebar-nav a').each(function(idx, link) {
|
||||||
|
routes.push($(link).attr('href'));
|
||||||
|
});
|
||||||
|
|
||||||
|
input.on('blur', function() {
|
||||||
|
$(this).val('').attr('placeholder', '/');
|
||||||
|
});
|
||||||
|
|
||||||
|
input.on('keyup focus', function() {
|
||||||
|
var $input = $(this),
|
||||||
|
value = $input.val().toLowerCase(),
|
||||||
|
menuItems = $('#acp-search .dropdown-menu').html('');
|
||||||
|
|
||||||
|
function toUpperCase(txt){
|
||||||
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
$input.attr('placeholder', '');
|
||||||
|
|
||||||
|
firstResult = null;
|
||||||
|
|
||||||
|
if (value.length >= 3) {
|
||||||
|
for (var file in acpIndex) {
|
||||||
|
if (acpIndex.hasOwnProperty(file)) {
|
||||||
|
var position = acpIndex[file].indexOf(value);
|
||||||
|
|
||||||
|
if (position !== -1) {
|
||||||
|
var href = file.replace('.tpl', ''),
|
||||||
|
title = href.replace(/^\/admin\//, '').split('/'),
|
||||||
|
description = acpIndex[file].substring(Math.max(0, position - 25), Math.min(acpIndex[file].length - 1, position + 25))
|
||||||
|
.replace(value, '<span class="search-match">' + value + '</span>');
|
||||||
|
|
||||||
|
for (var t in title) {
|
||||||
|
if (title.hasOwnProperty(t)) {
|
||||||
|
title[t] = title[t]
|
||||||
|
.replace('-', ' ')
|
||||||
|
.replace(/\w\S*/g, toUpperCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
title = title.join(' > ');
|
||||||
|
href = RELATIVE_PATH + href;
|
||||||
|
firstResult = firstResult ? firstResult : href;
|
||||||
|
|
||||||
|
if ($.inArray(href, routes) !== -1) {
|
||||||
|
menuItems.append('<li role="presentation"><a role="menuitem" href="' + href + '">' + title + '<br /><small><code>...' + description + '...</code></small></a></li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuItems.html() !== '') {
|
||||||
|
menuItems.append('<li role="presentation" class="divider"></li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length > 0) {
|
||||||
|
menuItems.append('<li role="presentation"><a role="menuitem" href="' + RELATIVE_PATH + '/search/' + value + '">Search the forum for <strong>' + value + '</strong></a></li>');
|
||||||
|
} else {
|
||||||
|
menuItems.append('<li role="presentation"><a role="menuitem" href="#">Start typing to see results...</a></li>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,445 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define, ajaxify, app, socket, RELATIVE_PATH*/
|
||||||
|
|
||||||
|
define('forum/admin/general/dashboard', ['semver'], function(semver) {
|
||||||
|
var Admin = {},
|
||||||
|
intervals = {
|
||||||
|
rooms: false,
|
||||||
|
graphs: false
|
||||||
|
},
|
||||||
|
isMobile = false;
|
||||||
|
|
||||||
|
|
||||||
|
Admin.init = function() {
|
||||||
|
app.enterRoom('admin');
|
||||||
|
socket.emit('meta.rooms.getAll', Admin.updateRoomUsage);
|
||||||
|
|
||||||
|
isMobile = !/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
intervals.rooms = setInterval(function() {
|
||||||
|
if (app.isFocused && app.isConnected) {
|
||||||
|
socket.emit('meta.rooms.getAll', Admin.updateRoomUsage);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.start', function(ev, data) {
|
||||||
|
clearInterval(intervals.rooms);
|
||||||
|
clearInterval(intervals.graphs);
|
||||||
|
|
||||||
|
intervals.rooms = null;
|
||||||
|
intervals.graphs = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#logout-link').on('click', function() {
|
||||||
|
$.post(RELATIVE_PATH + '/logout', function() {
|
||||||
|
window.location.href = RELATIVE_PATH + '/';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get('https://api.github.com/repos/NodeBB/NodeBB/tags', function(releases) {
|
||||||
|
// Re-sort the releases, as they do not follow Semver (wrt pre-releases)
|
||||||
|
releases = releases.sort(function(a, b) {
|
||||||
|
a = a.name.replace(/^v/, '');
|
||||||
|
b = b.name.replace(/^v/, '');
|
||||||
|
return semver.lt(a, b) ? 1 : -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
var version = $('#version').html(),
|
||||||
|
latestVersion = releases[0].name.slice(1),
|
||||||
|
checkEl = $('.version-check');
|
||||||
|
checkEl.html($('.version-check').html().replace('<i class="fa fa-spinner fa-spin"></i>', 'v' + latestVersion));
|
||||||
|
|
||||||
|
// Alter box colour accordingly
|
||||||
|
if (semver.eq(latestVersion, version)) {
|
||||||
|
checkEl.removeClass('alert-info').addClass('alert-success');
|
||||||
|
checkEl.append('<p>You are <strong>up-to-date</strong> <i class="fa fa-check"></i></p>');
|
||||||
|
} else if (semver.gt(latestVersion, version)) {
|
||||||
|
checkEl.removeClass('alert-info').addClass('alert-danger');
|
||||||
|
checkEl.append('<p>A new version (v' + latestVersion + ') has been released. Consider upgrading your NodeBB.</p>');
|
||||||
|
} else if (semver.gt(version, latestVersion)) {
|
||||||
|
checkEl.removeClass('alert-info').addClass('alert-warning');
|
||||||
|
checkEl.append('<p>You are running a <strong>development version</strong>! Unintended bugs may occur. <i class="fa fa-warning"></i></p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.restart').on('click', function() {
|
||||||
|
bootbox.confirm('Are you sure you wish to restart NodeBB?', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'instance_restart',
|
||||||
|
type: 'info',
|
||||||
|
title: 'Restarting... <i class="fa fa-spin fa-refresh"></i>',
|
||||||
|
message: 'NodeBB is restarting.',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).one('action:reconnected', function() {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'instance_restart',
|
||||||
|
type: 'success',
|
||||||
|
title: '<i class="fa fa-check"></i> Success',
|
||||||
|
message: 'NodeBB has successfully restarted.',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('admin.restart');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.reload').on('click', function() {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'instance_reload',
|
||||||
|
type: 'info',
|
||||||
|
title: 'Reloading... <i class="fa fa-spin fa-refresh"></i>',
|
||||||
|
message: 'NodeBB is reloading.',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('admin.reload', function(err) {
|
||||||
|
if (!err) {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'instance_reload',
|
||||||
|
type: 'success',
|
||||||
|
title: '<i class="fa fa-check"></i> Success',
|
||||||
|
message: 'NodeBB has successfully reloaded.',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'instance_reload',
|
||||||
|
type: 'danger',
|
||||||
|
title: '[[global:alert.error]]',
|
||||||
|
message: '[[error:reload-failed, ' + err.message + ']]'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setupGraphs();
|
||||||
|
};
|
||||||
|
|
||||||
|
Admin.updateRoomUsage = function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var html = '<div class="text-center pull-left">' +
|
||||||
|
'<div>'+ data.onlineRegisteredCount +'</div>' +
|
||||||
|
'<div>Users</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="text-center pull-left">' +
|
||||||
|
'<div>'+ data.onlineGuestCount +'</div>' +
|
||||||
|
'<div>Guests</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="text-center pull-left">' +
|
||||||
|
'<div>'+ (data.onlineRegisteredCount + data.onlineGuestCount) +'</div>' +
|
||||||
|
'<div>Total</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="text-center pull-left">' +
|
||||||
|
'<div>'+ data.socketCount +'</div>' +
|
||||||
|
'<div>Connections</div>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
var idle = data.socketCount - (data.users.home + data.users.topics + data.users.category);
|
||||||
|
|
||||||
|
updateRegisteredGraph(data.onlineRegisteredCount, data.onlineGuestCount);
|
||||||
|
updatePresenceGraph(data.users.home, data.users.topics, data.users.category, idle);
|
||||||
|
updateTopicsGraph(data.topics);
|
||||||
|
|
||||||
|
$('#active-users').html(html);
|
||||||
|
};
|
||||||
|
|
||||||
|
var graphs = {
|
||||||
|
traffic: null,
|
||||||
|
registered: null,
|
||||||
|
presence: null,
|
||||||
|
topics: null
|
||||||
|
};
|
||||||
|
|
||||||
|
var topicColors = ["#bf616a","#5B90BF","#d08770","#ebcb8b","#a3be8c","#96b5b4","#8fa1b3","#b48ead","#ab7967","#46BFBD"],
|
||||||
|
usedTopicColors = [];
|
||||||
|
|
||||||
|
// from chartjs.org
|
||||||
|
function lighten(col, amt) {
|
||||||
|
var usePound = false;
|
||||||
|
|
||||||
|
if (col[0] == "#") {
|
||||||
|
col = col.slice(1);
|
||||||
|
usePound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var num = parseInt(col,16);
|
||||||
|
|
||||||
|
var r = (num >> 16) + amt;
|
||||||
|
|
||||||
|
if (r > 255) r = 255;
|
||||||
|
else if (r < 0) r = 0;
|
||||||
|
|
||||||
|
var b = ((num >> 8) & 0x00FF) + amt;
|
||||||
|
|
||||||
|
if (b > 255) b = 255;
|
||||||
|
else if (b < 0) b = 0;
|
||||||
|
|
||||||
|
var g = (num & 0x0000FF) + amt;
|
||||||
|
|
||||||
|
if (g > 255) g = 255;
|
||||||
|
else if (g < 0) g = 0;
|
||||||
|
|
||||||
|
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHoursArray() {
|
||||||
|
var currentHour = new Date().getHours(),
|
||||||
|
labels = [];
|
||||||
|
|
||||||
|
for (var i = currentHour, ii = currentHour - 12; i > ii; i--) {
|
||||||
|
var hour = i < 0 ? 24 + i : i;
|
||||||
|
labels.push(hour + ':00 ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupGraphs() {
|
||||||
|
var trafficCanvas = document.getElementById('analytics-traffic'),
|
||||||
|
registeredCanvas = document.getElementById('analytics-registered'),
|
||||||
|
presenceCanvas = document.getElementById('analytics-presence'),
|
||||||
|
topicsCanvas = document.getElementById('analytics-topics'),
|
||||||
|
trafficCtx = trafficCanvas.getContext('2d'),
|
||||||
|
registeredCtx = registeredCanvas.getContext('2d'),
|
||||||
|
presenceCtx = presenceCanvas.getContext('2d'),
|
||||||
|
topicsCtx = topicsCanvas.getContext('2d'),
|
||||||
|
trafficLabels = getHoursArray();
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
Chart.defaults.global.showTooltips = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
labels: trafficLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Page Views",
|
||||||
|
fillColor: "rgba(220,220,220,0.2)",
|
||||||
|
strokeColor: "rgba(220,220,220,1)",
|
||||||
|
pointColor: "rgba(220,220,220,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(220,220,220,1)",
|
||||||
|
data: [0,0,0,0,0,0,0,0,0,0,0,0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Unique Visitors",
|
||||||
|
fillColor: "rgba(151,187,205,0.2)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||||
|
data: [0,0,0,0,0,0,0,0,0,0,0,0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
trafficCanvas.width = $(trafficCanvas).parent().width(); // is this necessary
|
||||||
|
graphs.traffic = new Chart(trafficCtx).Line(data, {
|
||||||
|
responsive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.registered = new Chart(registeredCtx).Doughnut([{
|
||||||
|
value: 1,
|
||||||
|
color:"#F7464A",
|
||||||
|
highlight: "#FF5A5E",
|
||||||
|
label: "Registered Users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
color: "#46BFBD",
|
||||||
|
highlight: "#5AD3D1",
|
||||||
|
label: "Anonymous Users"
|
||||||
|
}], {
|
||||||
|
responsive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.presence = new Chart(presenceCtx).Doughnut([{
|
||||||
|
value: 1,
|
||||||
|
color:"#F7464A",
|
||||||
|
highlight: "#FF5A5E",
|
||||||
|
label: "On homepage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
color: "#46BFBD",
|
||||||
|
highlight: "#5AD3D1",
|
||||||
|
label: "Reading posts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
color: "#FDB45C",
|
||||||
|
highlight: "#FFC870",
|
||||||
|
label: "Browsing topics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
color: "#949FB1",
|
||||||
|
highlight: "#A8B3C5",
|
||||||
|
label: "Idle"
|
||||||
|
}], {
|
||||||
|
responsive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.topics = new Chart(topicsCtx).Doughnut([], {responsive: true});
|
||||||
|
topicsCanvas.onclick = function(evt){
|
||||||
|
var obj = graphs.topics.getSegmentsAtEvent(evt);
|
||||||
|
window.open(RELATIVE_PATH + '/topic/' + obj[0].tid);
|
||||||
|
};
|
||||||
|
|
||||||
|
intervals.graphs = setInterval(updateTrafficGraph, 15000);
|
||||||
|
updateTrafficGraph();
|
||||||
|
|
||||||
|
$(window).on('resize', adjustPieCharts);
|
||||||
|
adjustPieCharts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustPieCharts() {
|
||||||
|
$('.pie-chart.legend-up').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
|
||||||
|
if ($this.width() < 320) {
|
||||||
|
$this.addClass('compact');
|
||||||
|
} else {
|
||||||
|
$this.removeClass('compact');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrafficGraph() {
|
||||||
|
if (!app.isFocused || !app.isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('admin.analytics.get', {graph: "traffic"}, function (err, data) {
|
||||||
|
for (var i = 0, ii = data.pageviews.length; i < ii; i++) {
|
||||||
|
graphs.traffic.datasets[0].points[i].value = data.pageviews[i];
|
||||||
|
graphs.traffic.datasets[1].points[i].value = data.uniqueVisitors[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentHour = new Date().getHours();
|
||||||
|
|
||||||
|
graphs.traffic.scale.xLabels = getHoursArray();
|
||||||
|
graphs.traffic.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRegisteredGraph(registered, anonymous) {
|
||||||
|
graphs.registered.segments[0].value = registered;
|
||||||
|
graphs.registered.segments[1].value = anonymous;
|
||||||
|
graphs.registered.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePresenceGraph(homepage, posts, topics, idle) {
|
||||||
|
graphs.presence.segments[0].value = homepage;
|
||||||
|
graphs.presence.segments[1].value = posts;
|
||||||
|
graphs.presence.segments[2].value = topics;
|
||||||
|
graphs.presence.segments[3].value = idle;
|
||||||
|
graphs.presence.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTopicsGraph(topics) {
|
||||||
|
if (!Object.keys(topics).length) {
|
||||||
|
topics = {"0": {
|
||||||
|
title: "No users browsing",
|
||||||
|
value: 1
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
var tids = Object.keys(topics),
|
||||||
|
segments = graphs.topics.segments;
|
||||||
|
|
||||||
|
function reassignExistingTopics() {
|
||||||
|
for (var i = 0, ii = segments.length; i < ii; i++ ) {
|
||||||
|
if (!segments[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tid = segments[i].tid;
|
||||||
|
|
||||||
|
if ($.inArray(tid, tids) === -1) {
|
||||||
|
usedTopicColors.splice($.inArray(segments[i].color, usedTopicColors), 1);
|
||||||
|
graphs.topics.removeData(i);
|
||||||
|
} else {
|
||||||
|
graphs.topics.segments[i].value = topics[tid].value;
|
||||||
|
delete topics[tid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignNewTopics() {
|
||||||
|
while (segments.length < 10 && tids.length > 0) {
|
||||||
|
var tid = tids.pop(),
|
||||||
|
data = topics[tid],
|
||||||
|
color = null;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tid === '0') {
|
||||||
|
color = '#4D5360';
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
for (var i = 0, ii = topicColors.length; i < ii; i++) {
|
||||||
|
var chosenColor = topicColors[i];
|
||||||
|
|
||||||
|
if ($.inArray(chosenColor, usedTopicColors) === -1) {
|
||||||
|
color = chosenColor;
|
||||||
|
usedTopicColors.push(color);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (color === null && usedTopicColors.length < topicColors.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color) {
|
||||||
|
graphs.topics.addData({
|
||||||
|
value: data.value,
|
||||||
|
color: color,
|
||||||
|
highlight: lighten(color, 10),
|
||||||
|
label: data.title
|
||||||
|
});
|
||||||
|
|
||||||
|
segments[segments.length - 1].tid = tid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTopicsLegend() {
|
||||||
|
var legend = $('#topics-legend').html('');
|
||||||
|
|
||||||
|
for (var i = 0, ii = segments.length; i < ii; i++) {
|
||||||
|
var topic = segments[i],
|
||||||
|
label = topic.tid === '0' ? topic.label : '<a title="' + topic.label + '"href="' + RELATIVE_PATH + '/topic/' + topic.tid + '" target="_blank"> ' + topic.label + '</a>';
|
||||||
|
|
||||||
|
legend.append(
|
||||||
|
'<li>' +
|
||||||
|
'<div style="background-color: ' + topic.highlightColor + '; border-color: ' + topic.strokeColor + '"></div>' +
|
||||||
|
'<span>' + label + '</span>' +
|
||||||
|
'</li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reassignExistingTopics();
|
||||||
|
assignNewTopics();
|
||||||
|
buildTopicsLegend();
|
||||||
|
|
||||||
|
graphs.topics.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTopicsLegend() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return Admin;
|
||||||
|
});
|
@ -0,0 +1,8 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define('forum/admin/general/languages', ['forum/admin/settings'], function(Settings) {
|
||||||
|
$(function() {
|
||||||
|
Settings.prepare();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,32 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, socket */
|
||||||
|
|
||||||
|
define('forum/admin/general/sounds', ['sounds', 'settings'], function(Sounds, Settings) {
|
||||||
|
var SoundsAdmin = {};
|
||||||
|
|
||||||
|
SoundsAdmin.init = function() {
|
||||||
|
// Sounds tab
|
||||||
|
$('.sounds').find('button[data-action="play"]').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var fileName = $(this).parent().parent().find('select').val();
|
||||||
|
Sounds.playFile(fileName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load Form Values
|
||||||
|
Settings.load('sounds', $('.sounds form'));
|
||||||
|
|
||||||
|
// Saving of Form Values
|
||||||
|
var saveEl = $('#save');
|
||||||
|
saveEl.on('click', function() {
|
||||||
|
Settings.save('sounds', $('.sounds form'), function() {
|
||||||
|
socket.emit('admin.fireEvent', {
|
||||||
|
name: 'event:sounds.reloadMapping'
|
||||||
|
});
|
||||||
|
app.alertSuccess('Settings Saved');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return SoundsAdmin;
|
||||||
|
});
|
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, bootbox */
|
||||||
|
|
||||||
|
define(function() {
|
||||||
|
var iconSelect = {};
|
||||||
|
|
||||||
|
iconSelect.init = function(el, onModified) {
|
||||||
|
onModified = onModified || function() {};
|
||||||
|
var selected = el.attr('class').replace('fa-2x', '').replace('fa', '').replace(/\s+/g, '');
|
||||||
|
$('#icons .selected').removeClass('selected');
|
||||||
|
|
||||||
|
if (selected === '') {
|
||||||
|
selected = 'fa-doesnt-exist';
|
||||||
|
}
|
||||||
|
if (selected) {
|
||||||
|
$('#icons .fa-icons .fa.' + selected).parent().addClass('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
bootbox.confirm('<h2>Select an icon.</h2>' + $('#icons').html(), function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
var iconClass = $('.bootbox .selected').attr('class');
|
||||||
|
var categoryIconClass = $('<div/>').addClass(iconClass).removeClass('fa').removeClass('selected').attr('class');
|
||||||
|
if (categoryIconClass === 'fa-doesnt-exist') {
|
||||||
|
categoryIconClass = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
el.attr('class', 'fa fa-2x ' + categoryIconClass);
|
||||||
|
el.val(categoryIconClass);
|
||||||
|
el.attr('value', categoryIconClass);
|
||||||
|
|
||||||
|
onModified(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function() { //bootbox was rewritten for BS3 and I had to add this timeout for the previous code to work. TODO: to look into
|
||||||
|
$('.bootbox .fa-icons i').on('click', function() {
|
||||||
|
$('.bootbox .selected').removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return iconSelect;
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,391 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define, socket, app, bootbox, templates, ajaxify, RELATIVE_PATH*/
|
||||||
|
|
||||||
|
define('forum/admin/manage/categories', ['uploader', 'forum/admin/iconSelect'], function(uploader, iconSelect) {
|
||||||
|
var Categories = {};
|
||||||
|
|
||||||
|
Categories.init = function() {
|
||||||
|
var modified_categories = {};
|
||||||
|
|
||||||
|
function modified(el) {
|
||||||
|
var cid = $(el).parents('li').attr('data-cid');
|
||||||
|
if(cid) {
|
||||||
|
modified_categories[cid] = modified_categories[cid] || {};
|
||||||
|
modified_categories[cid][$(el).attr('data-name')] = $(el).val();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
if(Object.keys(modified_categories).length) {
|
||||||
|
socket.emit('admin.categories.update', modified_categories, function(err, result) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result && result.length) {
|
||||||
|
app.alert({
|
||||||
|
title: 'Updated Categories',
|
||||||
|
message: 'Category IDs ' + result.join(', ') + ' was successfully updated.',
|
||||||
|
type: 'success',
|
||||||
|
timeout: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modified_categories = {};
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_blockclass(el) {
|
||||||
|
el.parentNode.parentNode.className = 'entry-row ' + el.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCategoryOrders() {
|
||||||
|
var categories = $('.admin-categories #entry-container').children();
|
||||||
|
for(var i = 0; i<categories.length; ++i) {
|
||||||
|
var input = $(categories[i]).find('input[data-name="order"]');
|
||||||
|
|
||||||
|
input.val(i+1).attr('data-value', i+1);
|
||||||
|
modified(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#entry-container').sortable({
|
||||||
|
stop: function(event, ui) {
|
||||||
|
updateCategoryOrders();
|
||||||
|
},
|
||||||
|
distance: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.blockclass, .admin-categories form select').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
$this.val($this.attr('data-value'));
|
||||||
|
});
|
||||||
|
|
||||||
|
function showCreateCategoryModal() {
|
||||||
|
$('#new-category-modal').modal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewCategory() {
|
||||||
|
var category = {
|
||||||
|
name: $('#inputName').val(),
|
||||||
|
description: $('#inputDescription').val(),
|
||||||
|
icon: $('#new-category-modal i').attr('value'),
|
||||||
|
bgColor: '#0059b2',
|
||||||
|
color: '#fff',
|
||||||
|
order: $('.admin-categories #entry-container').children().length + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('admin.categories.create', category, function(err, data) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'category_created',
|
||||||
|
title: 'Created',
|
||||||
|
message: 'Category successfully created!',
|
||||||
|
type: 'success',
|
||||||
|
timeout: 2000
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#new-category-modal').modal('hide');
|
||||||
|
ajaxify.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableColorPicker(idx, inputEl) {
|
||||||
|
var $inputEl = $(inputEl),
|
||||||
|
previewEl = $inputEl.parents('[data-cid]').find('.preview-box');
|
||||||
|
|
||||||
|
admin.enableColorPicker($inputEl, function(hsb, hex) {
|
||||||
|
if ($inputEl.attr('data-name') === 'bgColor') {
|
||||||
|
previewEl.css('background', '#' + hex);
|
||||||
|
} else if ($inputEl.attr('data-name') === 'color') {
|
||||||
|
previewEl.css('color', '#' + hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
modified($inputEl[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEditTargets() {
|
||||||
|
$('[data-edit-target]').on('click', function() {
|
||||||
|
var $this = $(this),
|
||||||
|
target = $($this.attr('data-edit-target'));
|
||||||
|
|
||||||
|
$this.addClass('hide');
|
||||||
|
target.removeClass('hide').on('blur', function() {
|
||||||
|
$this.removeClass('hide').children('span').html(this.value);
|
||||||
|
$(this).addClass('hide');
|
||||||
|
}).val($this.children('span').html());
|
||||||
|
|
||||||
|
target.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
var url = window.location.href,
|
||||||
|
parts = url.split('/'),
|
||||||
|
active = parts[parts.length - 1];
|
||||||
|
|
||||||
|
$('.nav-pills li').removeClass('active');
|
||||||
|
$('.nav-pills li a').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
if ($this.attr('href').match(active)) {
|
||||||
|
$this.parent().addClass('active');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$('#addNew').on('click', showCreateCategoryModal);
|
||||||
|
$('#create-category-btn').on('click', createNewCategory);
|
||||||
|
|
||||||
|
$('#entry-container, #new-category-modal').on('click', '.icon', function(ev) {
|
||||||
|
iconSelect.init($(this).find('i'), modified);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.admin-categories form input, .admin-categories form select').on('change', function(ev) {
|
||||||
|
modified(ev.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.dropdown').on('click', '[data-disabled]', function(ev) {
|
||||||
|
var btn = $(this),
|
||||||
|
categoryRow = btn.parents('li'),
|
||||||
|
cid = categoryRow.attr('data-cid'),
|
||||||
|
disabled = btn.attr('data-disabled') === 'false' ? '1' : '0';
|
||||||
|
|
||||||
|
categoryRow.remove();
|
||||||
|
modified_categories[cid] = modified_categories[cid] || {};
|
||||||
|
modified_categories[cid].disabled = disabled;
|
||||||
|
|
||||||
|
save();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Colour Picker
|
||||||
|
$('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker);
|
||||||
|
|
||||||
|
$('.admin-categories').on('click', '.save', save);
|
||||||
|
$('.admin-categories').on('click', '.purge', function() {
|
||||||
|
var categoryRow = $(this).parents('li[data-cid]');
|
||||||
|
var cid = categoryRow.attr('data-cid');
|
||||||
|
|
||||||
|
bootbox.confirm('Do you really want to purge this category "' + categoryRow.find('#cid-' + cid + '-name').val() + '"?<br/><strong class="text-danger">Warning!</strong> All topics and posts in this category will be purged!', function(confirm) {
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.emit('admin.categories.purge', cid, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
app.alertSuccess('Category purged!');
|
||||||
|
categoryRow.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.admin-categories').on('click', '.permissions', function() {
|
||||||
|
var cid = $(this).parents('li[data-cid]').attr('data-cid');
|
||||||
|
Categories.launchPermissionsModal(cid);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$('.admin-categories').on('click', '.upload-button', function() {
|
||||||
|
var inputEl = $(this),
|
||||||
|
cid = inputEl.parents('li[data-cid]').attr('data-cid');
|
||||||
|
|
||||||
|
uploader.open(RELATIVE_PATH + '/admin/category/uploadpicture', { cid: cid }, 0, function(imageUrlOnServer) {
|
||||||
|
inputEl.val(imageUrlOnServer);
|
||||||
|
var previewBox = inputEl.parents('li[data-cid]').find('.preview-box');
|
||||||
|
previewBox.css('background', 'url(' + imageUrlOnServer + '?' + new Date().getTime() + ')')
|
||||||
|
.css('background-size', 'cover');
|
||||||
|
modified(inputEl[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.admin-categories').on('click', '.delete-image', function() {
|
||||||
|
var parent = $(this).parents('li[data-cid]'),
|
||||||
|
inputEl = parent.find('.upload-button'),
|
||||||
|
preview = parent.find('.preview-box'),
|
||||||
|
bgColor = parent.find('.category_bgColor').val();
|
||||||
|
|
||||||
|
inputEl.val('');
|
||||||
|
modified(inputEl[0]);
|
||||||
|
|
||||||
|
preview.css('background', bgColor);
|
||||||
|
|
||||||
|
$(this).addClass('hide').hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#revertChanges').on('click', function() {
|
||||||
|
ajaxify.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
setupEditTargets();
|
||||||
|
|
||||||
|
$('button[data-action="setParent"]').on('click', function() {
|
||||||
|
var cid = $(this).parents('[data-cid]').attr('data-cid'),
|
||||||
|
modal = $('#setParent');
|
||||||
|
|
||||||
|
modal.find('select').val($(this).attr('data-parentCid'));
|
||||||
|
modal.attr('data-cid', cid).modal();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('button[data-action="removeParent"]').on('click', function() {
|
||||||
|
var cid = $(this).parents('[data-cid]').attr('data-cid');
|
||||||
|
var payload= {};
|
||||||
|
payload[cid] = {
|
||||||
|
parentCid: 0
|
||||||
|
};
|
||||||
|
socket.emit('admin.categories.update', payload, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
ajaxify.go('admin/manage/categories/active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#setParent [data-cid]').on('click', function() {
|
||||||
|
var modalEl = $('#setParent'),
|
||||||
|
parentCid = $(this).attr('data-cid'),
|
||||||
|
payload = {};
|
||||||
|
|
||||||
|
payload[modalEl.attr('data-cid')] = {
|
||||||
|
parentCid: parentCid
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('admin.categories.update', payload, function(err) {
|
||||||
|
modalEl.one('hidden.bs.modal', function() {
|
||||||
|
ajaxify.go('admin/manage/categories/active');
|
||||||
|
});
|
||||||
|
modalEl.modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Categories.launchPermissionsModal = function(cid) {
|
||||||
|
var modal = $('#category-permissions-modal'),
|
||||||
|
searchEl = modal.find('#permission-search'),
|
||||||
|
resultsEl = modal.find('.search-results.users'),
|
||||||
|
groupsResultsEl = modal.find('.search-results.groups'),
|
||||||
|
searchDelay;
|
||||||
|
|
||||||
|
// Clear the search field and results
|
||||||
|
searchEl.val('');
|
||||||
|
resultsEl.html('');
|
||||||
|
|
||||||
|
searchEl.off().on('keyup', function() {
|
||||||
|
var searchEl = this,
|
||||||
|
liEl;
|
||||||
|
|
||||||
|
clearTimeout(searchDelay);
|
||||||
|
|
||||||
|
searchDelay = setTimeout(function() {
|
||||||
|
socket.emit('admin.categories.search', {
|
||||||
|
username: searchEl.value,
|
||||||
|
cid: cid
|
||||||
|
}, function(err, results) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.parse('admin/partials/categories/users', {
|
||||||
|
users: results
|
||||||
|
}, function(html) {
|
||||||
|
resultsEl.html(html);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
Categories.refreshPrivilegeList(cid);
|
||||||
|
|
||||||
|
resultsEl.off().on('click', '[data-priv]', function(e) {
|
||||||
|
var anchorEl = $(this),
|
||||||
|
uid = anchorEl.parents('li[data-uid]').attr('data-uid'),
|
||||||
|
privilege = anchorEl.attr('data-priv');
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
socket.emit('admin.categories.setPrivilege', {
|
||||||
|
cid: cid,
|
||||||
|
uid: uid,
|
||||||
|
privilege: privilege,
|
||||||
|
set: !anchorEl.hasClass('active')
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
anchorEl.toggleClass('active', !anchorEl.hasClass('active'));
|
||||||
|
Categories.refreshPrivilegeList(cid);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.off().on('click', '.members li > img', function() {
|
||||||
|
searchEl.val($(this).attr('title'));
|
||||||
|
searchEl.keyup();
|
||||||
|
});
|
||||||
|
|
||||||
|
// User Groups and privileges
|
||||||
|
socket.emit('admin.categories.groupsList', cid, function(err, results) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.parse('admin/partials/categories/groups', {
|
||||||
|
groups: results
|
||||||
|
}, function(html) {
|
||||||
|
groupsResultsEl.html(html);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
groupsResultsEl.off().on('click', '[data-priv]', function(e) {
|
||||||
|
var anchorEl = $(this),
|
||||||
|
name = anchorEl.parents('li[data-name]').attr('data-name'),
|
||||||
|
privilege = anchorEl.attr('data-priv');
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
socket.emit('admin.categories.setGroupPrivilege', {
|
||||||
|
cid: cid,
|
||||||
|
name: name,
|
||||||
|
privilege: privilege,
|
||||||
|
set: !anchorEl.hasClass('active')
|
||||||
|
}, function(err) {
|
||||||
|
if (!err) {
|
||||||
|
anchorEl.toggleClass('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.modal();
|
||||||
|
};
|
||||||
|
|
||||||
|
Categories.refreshPrivilegeList = function (cid) {
|
||||||
|
var modalEl = $('#category-permissions-modal'),
|
||||||
|
memberList = $('.members');
|
||||||
|
|
||||||
|
socket.emit('admin.categories.getPrivilegeSettings', cid, function(err, privilegeList) {
|
||||||
|
var membersLength = privilegeList.length,
|
||||||
|
liEl, x, userObj;
|
||||||
|
|
||||||
|
memberList.html('');
|
||||||
|
if (membersLength > 0) {
|
||||||
|
for(x = 0; x < membersLength; x++) {
|
||||||
|
userObj = privilegeList[x];
|
||||||
|
liEl = $('<li/>').attr('data-uid', userObj.uid).html('<img src="' + userObj.picture + '" title="' + userObj.username + '" />');
|
||||||
|
memberList.append(liEl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
liEl = $('<li/>').addClass('empty').html('None.');
|
||||||
|
memberList.append(liEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Categories;
|
||||||
|
});
|
@ -0,0 +1,68 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define, socket, app, admin, utils, bootbox, RELATIVE_PATH*/
|
||||||
|
|
||||||
|
define('forum/admin/manage/flags', ['forum/infinitescroll', 'admin/selectable'], function(infinitescroll, selectable) {
|
||||||
|
var Flags = {};
|
||||||
|
|
||||||
|
Flags.init = function() {
|
||||||
|
handleDismiss();
|
||||||
|
handleDelete();
|
||||||
|
handleInfiniteScroll();
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleDismiss() {
|
||||||
|
$('.flags').on('click', '.dismiss', function() {
|
||||||
|
var btn = $(this);
|
||||||
|
var pid = btn.siblings('[data-pid]').attr('data-pid');
|
||||||
|
|
||||||
|
socket.emit('admin.dismissFlag', pid, function(err) {
|
||||||
|
done(err, btn);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete() {
|
||||||
|
$('.flags').on('click', '.delete', function() {
|
||||||
|
var btn = $(this);
|
||||||
|
var pid = btn.siblings('[data-pid]').attr('data-pid');
|
||||||
|
var tid = btn.siblings('[data-pid]').attr('data-tid');
|
||||||
|
socket.emit('posts.delete', {pid: pid, tid: tid}, function(err) {
|
||||||
|
done(err, btn);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function done(err, btn) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.messaage);
|
||||||
|
}
|
||||||
|
btn.parent().fadeOut(function() {
|
||||||
|
btn.remove();
|
||||||
|
});
|
||||||
|
if (!$('.flags [data-pid]').length) {
|
||||||
|
$('.post-container').text('No flagged posts!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInfiniteScroll() {
|
||||||
|
infinitescroll.init(function(direction) {
|
||||||
|
if (direction < 0 && !$('.flags').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('admin.getMoreFlags', $('[data-next]').attr('data-next'), function(data, done) {
|
||||||
|
if (data.posts && data.posts.length) {
|
||||||
|
infinitescroll.parseAndTranslate('admin/manage/flags', 'posts', {posts: data.posts}, function(html) {
|
||||||
|
$('[data-next]').attr('data-next', data.next);
|
||||||
|
$('.post-container').append(html);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Flags;
|
||||||
|
});
|
@ -0,0 +1,263 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define, templates, socket, ajaxify, app, bootbox*/
|
||||||
|
|
||||||
|
define('forum/admin/manage/groups', ['forum/admin/iconSelect'], function(iconSelect) {
|
||||||
|
var Groups = {};
|
||||||
|
|
||||||
|
Groups.init = function() {
|
||||||
|
var yourid = ajaxify.variables.get('yourid'),
|
||||||
|
createModal = $('#create-modal'),
|
||||||
|
createGroupName = $('#create-group-name'),
|
||||||
|
create = $('#create'),
|
||||||
|
createModalGo = $('#create-modal-go'),
|
||||||
|
createGroupDesc = $('#create-group-desc'),
|
||||||
|
createModalError = $('#create-modal-error'),
|
||||||
|
groupDetailsModal = $('#group-details-modal'),
|
||||||
|
groupDetailsSearch = $('#group-details-search'),
|
||||||
|
groupDetailsSearchResults = $('#group-details-search-results'),
|
||||||
|
groupMembersEl = $('ul.current_members'),
|
||||||
|
formEl = groupDetailsModal.find('form'),
|
||||||
|
detailsModalSave = $('#details-modal-save'),
|
||||||
|
groupsList = $('#groups-list'),
|
||||||
|
groupIcon = $('#group-icon'),
|
||||||
|
changeGroupIcon = $('#change-group-icon'),
|
||||||
|
changeGroupName = $('#change-group-name'),
|
||||||
|
changeGroupDesc = $('#change-group-desc'),
|
||||||
|
changeGroupUserTitle = $('#change-group-user-title'),
|
||||||
|
changeGroupLabelColor = $('#change-group-label-color'),
|
||||||
|
groupIcon = $('#group-icon'),
|
||||||
|
groupLabelPreview = $('#group-label-preview'),
|
||||||
|
searchDelay;
|
||||||
|
|
||||||
|
// Tooltips
|
||||||
|
$('#groups-list .members li').tooltip();
|
||||||
|
|
||||||
|
createModal.on('keypress', function(e) {
|
||||||
|
switch(e.keyCode) {
|
||||||
|
case 13:
|
||||||
|
createModalGo.click();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
create.on('click', function() {
|
||||||
|
createModal.modal('show');
|
||||||
|
setTimeout(function() {
|
||||||
|
createGroupName.focus();
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
createModalGo.on('click', function() {
|
||||||
|
var submitObj = {
|
||||||
|
name: createGroupName.val(),
|
||||||
|
description: createGroupDesc.val()
|
||||||
|
},
|
||||||
|
errorText;
|
||||||
|
|
||||||
|
socket.emit('admin.groups.create', submitObj, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
switch (err) {
|
||||||
|
case 'group-exists':
|
||||||
|
errorText = '<strong>Please choose another name</strong><p>There seems to be a group with this name already.</p>';
|
||||||
|
break;
|
||||||
|
case 'name-too-short':
|
||||||
|
errorText = '<strong>Please specify a group name</strong><p>A group name is required for administrative purposes.</p>';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorText = '<strong>Uh-Oh</strong><p>There was a problem creating your group. Please try again later!</p>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
createModalError.html(errorText).removeClass('hide');
|
||||||
|
} else {
|
||||||
|
createModalError.addClass('hide');
|
||||||
|
createGroupName.val('');
|
||||||
|
createModal.on('hidden.bs.modal', function() {
|
||||||
|
ajaxify.go('admin/groups');
|
||||||
|
});
|
||||||
|
createModal.modal('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
formEl.keypress(function(e) {
|
||||||
|
switch(e.keyCode) {
|
||||||
|
case 13:
|
||||||
|
detailsModalSave.click();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
changeGroupUserTitle.keydown(function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
groupLabelPreview.text(changeGroupUserTitle.val());
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
changeGroupLabelColor.keydown(function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
groupLabelPreview.css('background', changeGroupLabelColor.val() || '#000000');
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
groupsList.on('click', 'button[data-action]', function() {
|
||||||
|
var el = $(this),
|
||||||
|
action = el.attr('data-action'),
|
||||||
|
groupName = el.parents('li[data-groupname]').attr('data-groupname');
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'delete':
|
||||||
|
bootbox.confirm('Are you sure you wish to delete this group?', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('admin.groups.delete', groupName, function(err, data) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
ajaxify.go('admin/groups');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'members':
|
||||||
|
socket.emit('admin.groups.get', groupName, function(err, groupObj) {
|
||||||
|
|
||||||
|
changeGroupName.val(groupObj.name).prop('readonly', groupObj.system);
|
||||||
|
changeGroupDesc.val(groupObj.description);
|
||||||
|
changeGroupUserTitle.val(groupObj.userTitle);
|
||||||
|
groupIcon.attr('class', 'fa fa-2x ' + groupObj.icon).attr('value', groupObj.icon);
|
||||||
|
changeGroupLabelColor.val(groupObj.labelColor);
|
||||||
|
groupLabelPreview.css('background', groupObj.labelColor || '#000000').text(groupObj.userTitle);
|
||||||
|
groupMembersEl.empty();
|
||||||
|
|
||||||
|
if (groupObj.members.length > 0) {
|
||||||
|
for (var x = 0; x < groupObj.members.length; x++) {
|
||||||
|
var memberIcon = $('<li />')
|
||||||
|
.attr('data-uid', groupObj.members[x].uid)
|
||||||
|
.append($('<img />').attr('src', groupObj.members[x].picture))
|
||||||
|
.append($('<span />').html(groupObj.members[x].username));
|
||||||
|
groupMembersEl.append(memberIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupDetailsModal.attr('data-groupname', groupObj.name);
|
||||||
|
groupDetailsModal.modal('show');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
groupDetailsSearch.on('keyup', function() {
|
||||||
|
|
||||||
|
if (searchDelay) {
|
||||||
|
clearTimeout(searchDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
searchDelay = setTimeout(function() {
|
||||||
|
var searchText = groupDetailsSearch.val(),
|
||||||
|
foundUser;
|
||||||
|
|
||||||
|
socket.emit('admin.user.search', searchText, function(err, results) {
|
||||||
|
if (!err && results && results.users.length > 0) {
|
||||||
|
var numResults = results.users.length, x;
|
||||||
|
if (numResults > 4) {
|
||||||
|
numResults = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupDetailsSearchResults.empty();
|
||||||
|
for (x = 0; x < numResults; x++) {
|
||||||
|
foundUser = $('<li />');
|
||||||
|
foundUser
|
||||||
|
.attr({title: results.users[x].username, 'data-uid': results.users[x].uid})
|
||||||
|
.append($('<img />').attr('src', results.users[x].picture))
|
||||||
|
.append($('<span />').html(results.users[x].username));
|
||||||
|
|
||||||
|
groupDetailsSearchResults.append(foundUser);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
groupDetailsSearchResults.html('<li>No Users Found</li>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
groupDetailsSearchResults.on('click', 'li[data-uid]', function() {
|
||||||
|
var userLabel = $(this),
|
||||||
|
uid = parseInt(userLabel.attr('data-uid'), 10),
|
||||||
|
groupName = groupDetailsModal.attr('data-groupname'),
|
||||||
|
members = [];
|
||||||
|
|
||||||
|
groupMembersEl.find('li[data-uid]').each(function() {
|
||||||
|
members.push(parseInt($(this).attr('data-uid'), 10));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (members.indexOf(uid) === -1) {
|
||||||
|
socket.emit('admin.groups.join', {
|
||||||
|
groupName: groupName,
|
||||||
|
uid: uid
|
||||||
|
}, function(err, data) {
|
||||||
|
if (!err) {
|
||||||
|
groupMembersEl.append(userLabel.clone(true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
groupMembersEl.on('click', 'li[data-uid]', function() {
|
||||||
|
var uid = $(this).attr('data-uid'),
|
||||||
|
groupName = groupDetailsModal.attr('data-groupname');
|
||||||
|
|
||||||
|
socket.emit('admin.groups.get', groupName, function(err, groupObj){
|
||||||
|
if (!err){
|
||||||
|
bootbox.confirm('Are you sure you want to remove this user?', function(confirm) {
|
||||||
|
if (confirm){
|
||||||
|
socket.emit('admin.groups.leave', {
|
||||||
|
groupName: groupName,
|
||||||
|
uid: uid
|
||||||
|
}, function(err, data) {
|
||||||
|
if (!err) {
|
||||||
|
groupMembersEl.find('li[data-uid="' + uid + '"]').remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
changeGroupIcon.on('click', function() {
|
||||||
|
iconSelect.init(groupIcon);
|
||||||
|
});
|
||||||
|
|
||||||
|
admin.enableColorPicker(changeGroupLabelColor, function(hsb, hex) {
|
||||||
|
groupLabelPreview.css('background-color', '#' + hex);
|
||||||
|
});
|
||||||
|
|
||||||
|
detailsModalSave.on('click', function() {
|
||||||
|
socket.emit('admin.groups.update', {
|
||||||
|
groupName: groupDetailsModal.attr('data-groupname'),
|
||||||
|
values: {
|
||||||
|
name: changeGroupName.val(),
|
||||||
|
userTitle: changeGroupUserTitle.val(),
|
||||||
|
description: changeGroupDesc.val(),
|
||||||
|
icon: groupIcon.attr('value'),
|
||||||
|
labelColor: changeGroupLabelColor.val()
|
||||||
|
}
|
||||||
|
}, function(err) {
|
||||||
|
if (!err) {
|
||||||
|
groupDetailsModal.on('hidden.bs.modal', function() {
|
||||||
|
ajaxify.go('admin/groups');
|
||||||
|
});
|
||||||
|
groupDetailsModal.modal('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return Groups;
|
||||||
|
});
|
@ -0,0 +1,110 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define, socket, app, admin, utils, bootbox, RELATIVE_PATH*/
|
||||||
|
|
||||||
|
define('forum/admin/manage/tags', ['forum/infinitescroll', 'admin/selectable'], function(infinitescroll, selectable) {
|
||||||
|
var Tags = {},
|
||||||
|
timeoutId = 0;
|
||||||
|
|
||||||
|
Tags.init = function() {
|
||||||
|
handleColorPickers();
|
||||||
|
selectable.enable('.tag-management', '.tag-row');
|
||||||
|
|
||||||
|
$('#tag-search').on('input propertychange', function() {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(function() {
|
||||||
|
socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, tags) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.parseAndTranslate('admin/manage/tags', 'tags', {tags: tags}, function(html) {
|
||||||
|
$('.tag-list').html(html);
|
||||||
|
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||||
|
timeoutId = 0;
|
||||||
|
|
||||||
|
selectable.enable('.tag-management', '.tag-row');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#modify').on('click', function(ev) {
|
||||||
|
var tagsToModify = $('.tag-row.selected');
|
||||||
|
if (!tagsToModify.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstTag = $(tagsToModify[0]),
|
||||||
|
title = tagsToModify.length > 1 ? 'Editing multiple tags' : 'Editing ' + firstTag.find('.tag-item').text() + ' tag';
|
||||||
|
|
||||||
|
bootbox.dialog({
|
||||||
|
title: title,
|
||||||
|
message: firstTag.find('.tag-modal').html(),
|
||||||
|
buttons: {
|
||||||
|
success: {
|
||||||
|
label: "Save",
|
||||||
|
className: "btn-primary save",
|
||||||
|
callback: function() {
|
||||||
|
var modal = $('.bootbox'),
|
||||||
|
bgColor = modal.find('[data-name="bgColor"]').val(),
|
||||||
|
color = modal.find('[data-name="color"]').val();
|
||||||
|
|
||||||
|
tagsToModify.each(function(idx, tag) {
|
||||||
|
tag = $(tag);
|
||||||
|
|
||||||
|
tag.find('[data-name="bgColor"]').val(bgColor);
|
||||||
|
tag.find('[data-name="color"]').val(color);
|
||||||
|
tag.find('.tag-item').css('background-color', bgColor).css('color', color);
|
||||||
|
|
||||||
|
save(tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
handleColorPickers();
|
||||||
|
}, 500); // bootbox made me do it.
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleColorPickers() {
|
||||||
|
function enableColorPicker(idx, inputEl) {
|
||||||
|
var $inputEl = $(inputEl),
|
||||||
|
previewEl = $inputEl.parents('.tag-row').find('.tag-item');
|
||||||
|
|
||||||
|
admin.enableColorPicker($inputEl, function(hsb, hex) {
|
||||||
|
if ($inputEl.attr('data-name') === 'bgColor') {
|
||||||
|
previewEl.css('background-color', '#' + hex);
|
||||||
|
} else if ($inputEl.attr('data-name') === 'color') {
|
||||||
|
previewEl.css('color', '#' + hex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(tag) {
|
||||||
|
var data = {
|
||||||
|
tag: tag.attr('data-tag'),
|
||||||
|
bgColor : tag.find('[data-name="bgColor"]').val(),
|
||||||
|
color : tag.find('[data-name="color"]').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('admin.tags.update', data, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('Tag Updated!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Tags;
|
||||||
|
});
|
@ -0,0 +1,289 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global socket, define, templates, bootbox, app, ajaxify, */
|
||||||
|
define('forum/admin/manage/users', ['admin/selectable'], function(selectable) {
|
||||||
|
var Users = {};
|
||||||
|
|
||||||
|
Users.init = function() {
|
||||||
|
var yourid = ajaxify.variables.get('yourid');
|
||||||
|
|
||||||
|
selectable.enable('#users-container', '.user-selectable');
|
||||||
|
|
||||||
|
function getSelectedUids() {
|
||||||
|
var uids = [];
|
||||||
|
$('#users-container .users-box .selected').each(function() {
|
||||||
|
uids.push($(this).parents('[data-uid]').attr('data-uid'));
|
||||||
|
});
|
||||||
|
|
||||||
|
return uids;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(className, state) {
|
||||||
|
$('#users-container .users-box .selected').siblings('.labels').find(className).each(function() {
|
||||||
|
$(this).toggleClass('hide', !state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function unselectAll() {
|
||||||
|
$('#users-container .users-box .selected').removeClass('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSelected() {
|
||||||
|
$('#users-container .users-box .selected').remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function done(successMessage, className, flag) {
|
||||||
|
return function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
app.alertSuccess(successMessage);
|
||||||
|
if (className) {
|
||||||
|
update(className, flag);
|
||||||
|
}
|
||||||
|
unselectAll();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.ban-user').on('click', function() {
|
||||||
|
var uids = getSelectedUids();
|
||||||
|
if (!uids.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bootbox.confirm('Do you really want to ban?', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('admin.user.banUsers', uids, done('User(s) banned!', '.ban', true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.unban-user').on('click', function() {
|
||||||
|
var uids = getSelectedUids();
|
||||||
|
if (!uids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('admin.user.unbanUsers', uids, done('User(s) unbanned!', '.ban', false));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.reset-lockout').on('click', function() {
|
||||||
|
var uids = getSelectedUids();
|
||||||
|
if (!uids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('admin.user.resetLockouts', uids, done('Lockout(s) reset!'));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.admin-user').on('click', function() {
|
||||||
|
var uids = getSelectedUids();
|
||||||
|
if (!uids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uids.indexOf(yourid) !== -1) {
|
||||||
|
app.alertError('You can\'t remove yourself as Administrator!');
|
||||||
|
} else {
|
||||||
|
socket.emit('admin.user.makeAdmins', uids, done('User(s) are now administrators.', '.administrator', true));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.remove-admin-user').on('click', function() {
|
||||||
|
var uids = getSelectedUids();
|
||||||
|
if (!uids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uids.indexOf(yourid.toString()) !== -1) {
|
||||||
|
app.alertError('You can\'t remove yourself as Administrator!');
|
||||||
|
} else {
|
||||||
|
bootbox.confirm('Do you really want to remove admins?', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('admin.user.removeAdmins', uids, done('User(s) are no longer administrators.', '.administrator', false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.validate-email').on('click', function() {
|
||||||
|
var uids = getSelectedUids();
|
||||||
|
if (!uids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bootbox.confirm('Do you want to validate email(s) of these user(s)?', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('admin.user.validateEmail', uids, done('Emails validated', '.notvalidated', false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.delete-user').on('click', function() {
|
||||||
|
var uids = getSelectedUids();
|
||||||
|
if (!uids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bootbox.confirm('<b>Warning!</b><br/>Do you really want to delete user(s)?<br/> This action is not reversable, all user data and content will be erased!', function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('admin.user.deleteUsers', uids, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('User(s) Deleted!');
|
||||||
|
removeSelected();
|
||||||
|
unselectAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleUserCreate() {
|
||||||
|
var errorEl = $('#create-modal-error');
|
||||||
|
$('#createUser').on('click', function() {
|
||||||
|
$('#create-modal').modal('show');
|
||||||
|
$('#create-modal form')[0].reset();
|
||||||
|
errorEl.addClass('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#create-modal-go').on('click', function() {
|
||||||
|
var username = $('#create-user-name').val(),
|
||||||
|
email = $('#create-user-email').val(),
|
||||||
|
password = $('#create-user-password').val(),
|
||||||
|
passwordAgain = $('#create-user-password-again').val();
|
||||||
|
|
||||||
|
|
||||||
|
if(password !== passwordAgain) {
|
||||||
|
return errorEl.html('<strong>Error</strong><p>Passwords must match!</p>').removeClass('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = {
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('admin.user.createUser', user, function(err) {
|
||||||
|
if(err) {
|
||||||
|
return errorEl.html('<strong>Error</strong><p>' + err.message + '</p>').removeClass('hide');
|
||||||
|
}
|
||||||
|
$('#create-modal').modal('hide');
|
||||||
|
$('#create-modal').on('hidden.bs.modal', function() {
|
||||||
|
ajaxify.go('admin/users');
|
||||||
|
});
|
||||||
|
app.alertSuccess('User created!');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeoutId = 0,
|
||||||
|
loadingMoreUsers = false;
|
||||||
|
|
||||||
|
var url = window.location.href,
|
||||||
|
parts = url.split('/'),
|
||||||
|
active = parts[parts.length - 1];
|
||||||
|
|
||||||
|
$('.nav-pills li').removeClass('active');
|
||||||
|
$('.nav-pills li a').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
if ($this.attr('href').match(active)) {
|
||||||
|
$this.parent().addClass('active');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#search-user').on('keyup', function() {
|
||||||
|
if (timeoutId !== 0) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(function() {
|
||||||
|
var username = $('#search-user').val();
|
||||||
|
|
||||||
|
$('.fa-spinner').removeClass('hidden');
|
||||||
|
|
||||||
|
socket.emit('admin.user.search', username, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.parse('admin/manage/users', 'users', data, function(html) {
|
||||||
|
$('#users-container').html(html);
|
||||||
|
|
||||||
|
$('.fa-spinner').addClass('hidden');
|
||||||
|
|
||||||
|
if (data && data.users.length === 0) {
|
||||||
|
$('#user-notfound-notify').html('User not found!')
|
||||||
|
.show()
|
||||||
|
.addClass('label-danger')
|
||||||
|
.removeClass('label-success');
|
||||||
|
} else {
|
||||||
|
$('#user-notfound-notify').html(data.users.length + ' user' + (data.users.length > 1 ? 's' : '') + ' found! Search took ' + data.timing + ' ms.')
|
||||||
|
.show()
|
||||||
|
.addClass('label-success')
|
||||||
|
.removeClass('label-danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
selectable.enable('#users-container', '.user-selectable');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
handleUserCreate();
|
||||||
|
|
||||||
|
function onUsersLoaded(users) {
|
||||||
|
templates.parse('admin/manage/users', 'users', {users: users}, function(html) {
|
||||||
|
$('#users-container').append($(html));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMoreUsers() {
|
||||||
|
var set = '';
|
||||||
|
if (active === 'latest') {
|
||||||
|
set = 'users:joindate';
|
||||||
|
} else if (active === 'sort-posts') {
|
||||||
|
set = 'users:postcount';
|
||||||
|
} else if (active === 'sort-reputation') {
|
||||||
|
set = 'users:reputation';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (set) {
|
||||||
|
loadingMoreUsers = true;
|
||||||
|
socket.emit('user.loadMore', {
|
||||||
|
set: set,
|
||||||
|
after: $('#users-container').children().length
|
||||||
|
}, function(err, data) {
|
||||||
|
if (data && data.users.length) {
|
||||||
|
onUsersLoaded(data.users);
|
||||||
|
}
|
||||||
|
loadingMoreUsers = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#load-more-users-btn').on('click', loadMoreUsers);
|
||||||
|
|
||||||
|
$(window).off('scroll').on('scroll', function() {
|
||||||
|
var bottom = ($(document).height() - $(window).height()) * 0.9;
|
||||||
|
|
||||||
|
if ($(window).scrollTop() > bottom && !loadingMoreUsers) {
|
||||||
|
loadMoreUsers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return Users;
|
||||||
|
});
|
@ -0,0 +1,169 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global define, app, socket, ajaxify, RELATIVE_PATH */
|
||||||
|
|
||||||
|
define('forum/admin/settings', ['uploader', 'sounds'], function(uploader, sounds) {
|
||||||
|
var Settings = {};
|
||||||
|
|
||||||
|
Settings.init = function() {
|
||||||
|
Settings.prepare();
|
||||||
|
};
|
||||||
|
|
||||||
|
Settings.prepare = function(callback) {
|
||||||
|
// Come back in 125ms if the config isn't ready yet
|
||||||
|
if (!app.config) {
|
||||||
|
setTimeout(function() {
|
||||||
|
Settings.prepare(callback);
|
||||||
|
}, 125);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the fields on the page from the config
|
||||||
|
var fields = $('#content [data-field]'),
|
||||||
|
numFields = fields.length,
|
||||||
|
saveBtn = $('#save'),
|
||||||
|
revertBtn = $('#revert'),
|
||||||
|
x, key, inputType, field;
|
||||||
|
|
||||||
|
for (x = 0; x < numFields; x++) {
|
||||||
|
field = fields.eq(x);
|
||||||
|
key = field.attr('data-field');
|
||||||
|
inputType = field.attr('type');
|
||||||
|
if (field.is('input')) {
|
||||||
|
if (app.config[key]) {
|
||||||
|
switch (inputType) {
|
||||||
|
case 'text':
|
||||||
|
case 'hidden':
|
||||||
|
case 'password':
|
||||||
|
case 'textarea':
|
||||||
|
case 'number':
|
||||||
|
field.val(app.config[key]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'checkbox':
|
||||||
|
field.prop('checked', parseInt(app.config[key], 10) === 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (field.is('textarea')) {
|
||||||
|
if (app.config[key]) {
|
||||||
|
field.val(app.config[key]);
|
||||||
|
}
|
||||||
|
} else if (field.is('select')) {
|
||||||
|
if (app.config[key]) {
|
||||||
|
field.val(app.config[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revertBtn.off('click').on('click', function(e) {
|
||||||
|
ajaxify.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
saveBtn.off('click').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
saveFields(fields, function onFieldsSaved(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alert({
|
||||||
|
alert_id: 'config_status',
|
||||||
|
timeout: 2500,
|
||||||
|
title: 'Changes Not Saved',
|
||||||
|
message: 'NodeBB encountered a problem saving your changes',
|
||||||
|
type: 'danger'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'config_status',
|
||||||
|
timeout: 2500,
|
||||||
|
title: 'Changes Saved',
|
||||||
|
message: 'Your changes to the NodeBB configuration have been saved.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
handleUploads();
|
||||||
|
|
||||||
|
$('button[data-action="email.test"]').off('click').on('click', function() {
|
||||||
|
socket.emit('admin.email.test', function(err) {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'test_email_sent',
|
||||||
|
type: !err ? 'info' : 'danger',
|
||||||
|
title: 'Test Email Sent',
|
||||||
|
message: err ? err.message : '',
|
||||||
|
timeout: 2500
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleUploads() {
|
||||||
|
$('#content input[data-action="upload"]').each(function() {
|
||||||
|
var uploadBtn = $(this);
|
||||||
|
uploadBtn.on('click', function() {
|
||||||
|
uploader.open(uploadBtn.attr('data-route'), {}, 0, function(image) {
|
||||||
|
$('#' + uploadBtn.attr('data-target')).val(image);
|
||||||
|
});
|
||||||
|
|
||||||
|
uploader.hideAlerts();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.remove = function(key) {
|
||||||
|
socket.emit('admin.config.remove', key);
|
||||||
|
};
|
||||||
|
|
||||||
|
function saveFields(fields, callback) {
|
||||||
|
var data = {};
|
||||||
|
|
||||||
|
fields.each(function() {
|
||||||
|
var field = $(this);
|
||||||
|
var key = field.attr('data-field'),
|
||||||
|
value, inputType;
|
||||||
|
|
||||||
|
if (field.is('input')) {
|
||||||
|
inputType = field.attr('type');
|
||||||
|
switch (inputType) {
|
||||||
|
case 'text':
|
||||||
|
case 'password':
|
||||||
|
case 'hidden':
|
||||||
|
case 'textarea':
|
||||||
|
case 'number':
|
||||||
|
value = field.val();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'checkbox':
|
||||||
|
value = field.prop('checked') ? '1' : '0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (field.is('textarea') || field.is('select')) {
|
||||||
|
value = field.val();
|
||||||
|
}
|
||||||
|
|
||||||
|
data[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('admin.config.setMultiple', data, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.config) {
|
||||||
|
for(var field in data) {
|
||||||
|
if (data.hasOwnProperty(field)) {
|
||||||
|
app.config[field] = data[field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Settings;
|
||||||
|
});
|
@ -0,0 +1,336 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, ajaxify, socket, app, config, utils, translator, bootbox */
|
||||||
|
|
||||||
|
define('forum/account/edit', ['forum/account/header', 'uploader'], function(header, uploader) {
|
||||||
|
var AccountEdit = {},
|
||||||
|
gravatarPicture = '',
|
||||||
|
uploadedPicture = '',
|
||||||
|
selectedImageType = '',
|
||||||
|
currentEmail;
|
||||||
|
|
||||||
|
AccountEdit.init = function() {
|
||||||
|
gravatarPicture = ajaxify.variables.get('gravatarpicture');
|
||||||
|
uploadedPicture = ajaxify.variables.get('uploadedpicture');
|
||||||
|
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
$('#submitBtn').on('click', updateProfile);
|
||||||
|
|
||||||
|
$('#inputBirthday').datepicker({
|
||||||
|
changeMonth: true,
|
||||||
|
changeYear: true,
|
||||||
|
yearRange: '1900:+0'
|
||||||
|
});
|
||||||
|
|
||||||
|
currentEmail = $('#inputEmail').val();
|
||||||
|
|
||||||
|
handleImageChange();
|
||||||
|
handleAccountDelete();
|
||||||
|
handleImageUpload();
|
||||||
|
handleEmailConfirm();
|
||||||
|
handlePasswordChange();
|
||||||
|
updateSignature();
|
||||||
|
updateImages();
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateProfile() {
|
||||||
|
var userData = {
|
||||||
|
uid: $('#inputUID').val(),
|
||||||
|
username: $('#inputUsername').val(),
|
||||||
|
email: $('#inputEmail').val(),
|
||||||
|
fullname: $('#inputFullname').val(),
|
||||||
|
website: $('#inputWebsite').val(),
|
||||||
|
birthday: $('#inputBirthday').val(),
|
||||||
|
location: $('#inputLocation').val(),
|
||||||
|
signature: $('#inputSignature').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('user.updateProfile', userData, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('[[user:profile_update_success]]');
|
||||||
|
|
||||||
|
if (data.picture) {
|
||||||
|
$('#user-current-picture').attr('src', data.picture);
|
||||||
|
$('#user_label img').attr('src', data.picture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.gravatarpicture) {
|
||||||
|
$('#user-gravatar-picture').attr('src', data.gravatarpicture);
|
||||||
|
gravatarPicture = data.gravatarpicture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.userslug) {
|
||||||
|
var oldslug = $('.account-username-box').attr('data-userslug');
|
||||||
|
$('.account-username-box a').each(function(index) {
|
||||||
|
$(this).attr('href', $(this).attr('href').replace(oldslug, data.userslug));
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.account-username-box').attr('data-userslug', data.userslug);
|
||||||
|
|
||||||
|
$('#user-profile-link').attr('href', config.relative_path + '/user/' + data.userslug);
|
||||||
|
$('#user-header-name').text(userData.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentEmail !== data.email) {
|
||||||
|
currentEmail = data.email;
|
||||||
|
$('#confirm-email').removeClass('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImageChange() {
|
||||||
|
function selectImageType(type) {
|
||||||
|
$('#gravatar-box .fa-check').toggle(type === 'gravatar');
|
||||||
|
$('#uploaded-box .fa-check').toggle(type === 'uploaded');
|
||||||
|
selectedImageType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#changePictureBtn').on('click', function() {
|
||||||
|
selectedImageType = '';
|
||||||
|
updateImages();
|
||||||
|
|
||||||
|
$('#change-picture-modal').modal('show');
|
||||||
|
$('#change-picture-modal').removeClass('hide');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#gravatar-box').on('click', function() {
|
||||||
|
selectImageType('gravatar');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#uploaded-box').on('click', function() {
|
||||||
|
selectImageType('uploaded');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#savePictureChangesBtn').on('click', function() {
|
||||||
|
$('#change-picture-modal').modal('hide');
|
||||||
|
|
||||||
|
if (selectedImageType) {
|
||||||
|
changeUserPicture(selectedImageType);
|
||||||
|
|
||||||
|
if (selectedImageType === 'gravatar') {
|
||||||
|
$('#user-current-picture').attr('src', gravatarPicture);
|
||||||
|
$('#user-header-picture').attr('src', gravatarPicture);
|
||||||
|
} else if (selectedImageType === 'uploaded') {
|
||||||
|
$('#user-current-picture').attr('src', uploadedPicture);
|
||||||
|
$('#user-header-picture').attr('src', uploadedPicture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAccountDelete() {
|
||||||
|
$('#deleteAccountBtn').on('click', function() {
|
||||||
|
translator.translate('[[user:delete_account_confirm]]', function(translated) {
|
||||||
|
bootbox.confirm(translated + '<p><input type="text" class="form-control" id="confirm-username" /></p>', function(confirm) {
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($('#confirm-username').val() !== app.username) {
|
||||||
|
app.alertError('[[error:invalid-username]]');
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
socket.emit('user.deleteAccount', {}, function(err) {
|
||||||
|
if (!err) {
|
||||||
|
app.logout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImageUpload() {
|
||||||
|
function onUploadComplete(urlOnServer) {
|
||||||
|
urlOnServer = urlOnServer + '?' + new Date().getTime();
|
||||||
|
|
||||||
|
$('#user-current-picture').attr('src', urlOnServer);
|
||||||
|
$('#user-uploaded-picture').attr('src', urlOnServer);
|
||||||
|
$('#user-header-picture').attr('src', urlOnServer);
|
||||||
|
uploadedPicture = urlOnServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$('#upload-picture-modal').on('hide', function() {
|
||||||
|
$('#userPhotoInput').val('');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#uploadPictureBtn').on('click', function() {
|
||||||
|
|
||||||
|
$('#change-picture-modal').modal('hide');
|
||||||
|
uploader.open(config.relative_path + '/api/user/' + ajaxify.variables.get('userslug') + '/uploadpicture', {}, config.maximumProfileImageSize, function(imageUrlOnServer) {
|
||||||
|
onUploadComplete(imageUrlOnServer);
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#uploadFromUrlBtn').on('click', function() {
|
||||||
|
$('#change-picture-modal').modal('hide');
|
||||||
|
var uploadModal = $('#upload-picture-from-url-modal');
|
||||||
|
uploadModal.modal('show').removeClass('hide');
|
||||||
|
|
||||||
|
uploadModal.find('.upload-btn').on('click', function() {
|
||||||
|
var url = uploadModal.find('#uploadFromUrl').val();
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.emit('user.uploadProfileImageFromUrl', url, function(err, imageUrlOnServer) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
onUploadComplete(imageUrlOnServer);
|
||||||
|
|
||||||
|
uploadModal.modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEmailConfirm() {
|
||||||
|
$('#confirm-email').on('click', function() {
|
||||||
|
socket.emit('user.emailConfirm', {}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
app.alertSuccess('[[notifications:email-confirm-sent]]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePasswordChange() {
|
||||||
|
var currentPassword = $('#inputCurrentPassword');
|
||||||
|
var password_notify = $('#password-notify');
|
||||||
|
var password_confirm_notify = $('#password-confirm-notify');
|
||||||
|
var password = $('#inputNewPassword');
|
||||||
|
var password_confirm = $('#inputNewPasswordAgain');
|
||||||
|
var passwordvalid = false;
|
||||||
|
var passwordsmatch = false;
|
||||||
|
var successIcon = '<i class="fa fa-check"></i>';
|
||||||
|
|
||||||
|
function onPasswordChanged() {
|
||||||
|
passwordvalid = utils.isPasswordValid(password.val());
|
||||||
|
if (password.val().length < config.minimumPasswordLength) {
|
||||||
|
showError(password_notify, '[[user:change_password_error_length]]');
|
||||||
|
} else if (!passwordvalid) {
|
||||||
|
showError(password_notify, '[[user:change_password_error]]');
|
||||||
|
} else {
|
||||||
|
showSuccess(password_notify, successIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPasswordConfirmChanged() {
|
||||||
|
if(password.val()) {
|
||||||
|
if (password.val() !== password_confirm.val()) {
|
||||||
|
showError(password_confirm_notify, '[[user:change_password_error_match]]');
|
||||||
|
passwordsmatch = false;
|
||||||
|
} else {
|
||||||
|
showSuccess(password_confirm_notify, successIcon);
|
||||||
|
passwordsmatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
password.on('blur', onPasswordChanged);
|
||||||
|
password_confirm.on('blur', onPasswordConfirmChanged);
|
||||||
|
|
||||||
|
$('#changePasswordBtn').on('click', function() {
|
||||||
|
if ((passwordvalid && passwordsmatch) || app.isAdmin) {
|
||||||
|
socket.emit('user.changePassword', {
|
||||||
|
'currentPassword': currentPassword.val(),
|
||||||
|
'newPassword': password.val(),
|
||||||
|
'uid': ajaxify.variables.get('theirid')
|
||||||
|
}, function(err) {
|
||||||
|
currentPassword.val('');
|
||||||
|
password.val('');
|
||||||
|
password_confirm.val('');
|
||||||
|
passwordsmatch = false;
|
||||||
|
passwordvalid = false;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('[[user:change_password_success]]');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeUserPicture(type) {
|
||||||
|
socket.emit('user.changePicture', {
|
||||||
|
type: type,
|
||||||
|
uid: ajaxify.variables.get('theirid')
|
||||||
|
}, function(err) {
|
||||||
|
if(err) {
|
||||||
|
app.alertError(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateImages() {
|
||||||
|
var currentPicture = $('#user-current-picture').attr('src');
|
||||||
|
|
||||||
|
if (gravatarPicture) {
|
||||||
|
$('#user-gravatar-picture').attr('src', gravatarPicture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadedPicture) {
|
||||||
|
$('#user-uploaded-picture').attr('src', uploadedPicture);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#gravatar-box').toggle(!!gravatarPicture);
|
||||||
|
$('#uploaded-box').toggle(!!uploadedPicture);
|
||||||
|
|
||||||
|
$('#gravatar-box .fa-check').toggle(currentPicture !== uploadedPicture);
|
||||||
|
$('#uploaded-box .fa-check').toggle(currentPicture === uploadedPicture);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSignature() {
|
||||||
|
function getSignatureCharsLeft() {
|
||||||
|
return $('#inputSignature').length ? '(' + $('#inputSignature').val().length + '/' + config.maximumSignatureLength + ')' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#signatureCharCountLeft').html(getSignatureCharsLeft());
|
||||||
|
|
||||||
|
$('#inputSignature').on('keyup change', function(ev) {
|
||||||
|
$('#signatureCharCountLeft').html(getSignatureCharsLeft());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(element, msg) {
|
||||||
|
translator.translate(msg, function(msg) {
|
||||||
|
element.html(msg);
|
||||||
|
element.parent()
|
||||||
|
.removeClass('alert-success')
|
||||||
|
.addClass('alert-danger');
|
||||||
|
element.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(element, msg) {
|
||||||
|
translator.translate(msg, function(msg) {
|
||||||
|
element.html(msg);
|
||||||
|
element.parent()
|
||||||
|
.removeClass('alert-danger')
|
||||||
|
.addClass('alert-success');
|
||||||
|
element.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountEdit;
|
||||||
|
});
|
@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, utils */
|
||||||
|
|
||||||
|
define('forum/account/favourites', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) {
|
||||||
|
var Favourites = {};
|
||||||
|
|
||||||
|
Favourites.init = function() {
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
$('.user-favourite-posts img').addClass('img-responsive');
|
||||||
|
|
||||||
|
infinitescroll.init(loadMore);
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadMore(direction) {
|
||||||
|
if (direction < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('posts.loadMoreFavourites', {
|
||||||
|
after: $('.user-favourite-posts').attr('data-nextstart')
|
||||||
|
}, function(data, done) {
|
||||||
|
if (data.posts && data.posts.length) {
|
||||||
|
onPostsLoaded(data.posts, done);
|
||||||
|
$('.user-favourite-posts').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostsLoaded(posts, callback) {
|
||||||
|
infinitescroll.parseAndTranslate('account/favourites', 'posts', {posts: posts}, function(html) {
|
||||||
|
$('.user-favourite-posts').append(html);
|
||||||
|
html.find('img').addClass('img-responsive');
|
||||||
|
html.find('span.timeago').timeago();
|
||||||
|
app.createUserTooltips();
|
||||||
|
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Favourites;
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
define('forum/account/followers', ['forum/account/header'], function(header) {
|
||||||
|
var Followers = {};
|
||||||
|
|
||||||
|
Followers.init = function() {
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
var yourid = ajaxify.variables.get('yourid'),
|
||||||
|
theirid = ajaxify.variables.get('theirid'),
|
||||||
|
followersCount = ajaxify.variables.get('followersCount');
|
||||||
|
|
||||||
|
|
||||||
|
if (parseInt(followersCount, 10) === 0) {
|
||||||
|
$('#no-followers-notice').removeClass('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return Followers;
|
||||||
|
});
|
@ -0,0 +1,15 @@
|
|||||||
|
define('forum/account/following', ['forum/account/header'], function(header) {
|
||||||
|
var Following = {};
|
||||||
|
|
||||||
|
Following.init = function() {
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
var followingCount = ajaxify.variables.get('followingCount');
|
||||||
|
|
||||||
|
if (parseInt(followingCount, 10) === 0) {
|
||||||
|
$('#no-following-notice').removeClass('hide');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Following;
|
||||||
|
});
|
@ -0,0 +1,34 @@
|
|||||||
|
define('forum/account/header', function() {
|
||||||
|
var AccountHeader = {};
|
||||||
|
|
||||||
|
AccountHeader.init = function() {
|
||||||
|
displayAccountMenus();
|
||||||
|
selectActivePill();
|
||||||
|
};
|
||||||
|
|
||||||
|
function displayAccountMenus() {
|
||||||
|
var yourid = ajaxify.variables.get('yourid'),
|
||||||
|
theirid = ajaxify.variables.get('theirid');
|
||||||
|
|
||||||
|
if (parseInt(yourid, 10) !== 0 && parseInt(yourid, 10) === parseInt(theirid, 10)) {
|
||||||
|
$('#editLink, #settingsLink, #favouritesLink').removeClass('hide');
|
||||||
|
} else {
|
||||||
|
$('.account-sub-links .plugin-link').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
$this.toggleClass('hide', $this.hasClass('private'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectActivePill() {
|
||||||
|
$('.account-sub-links li').removeClass('active').each(function() {
|
||||||
|
var href = $(this).find('a').attr('href');
|
||||||
|
if (window.location.href.indexOf(href) !== -1) {
|
||||||
|
$(this).addClass('active');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountHeader;
|
||||||
|
});
|
@ -0,0 +1,46 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, socket, utils */
|
||||||
|
|
||||||
|
define('forum/account/posts', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) {
|
||||||
|
var AccountPosts = {};
|
||||||
|
|
||||||
|
AccountPosts.init = function() {
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
$('.user-favourite-posts img').addClass('img-responsive');
|
||||||
|
|
||||||
|
infinitescroll.init(loadMore);
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadMore(direction) {
|
||||||
|
if (direction < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('posts.loadMoreUserPosts', {
|
||||||
|
uid: $('.account-username-box').attr('data-uid'),
|
||||||
|
after: $('.user-favourite-posts').attr('data-nextstart')
|
||||||
|
}, function(data, done) {
|
||||||
|
if (data.posts && data.posts.length) {
|
||||||
|
onPostsLoaded(data.posts, done);
|
||||||
|
$('.user-favourite-posts').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostsLoaded(posts, callback) {
|
||||||
|
infinitescroll.parseAndTranslate('account/posts', 'posts', {posts: posts}, function(html) {
|
||||||
|
$('.user-favourite-posts').append(html);
|
||||||
|
html.find('img').addClass('img-responsive');
|
||||||
|
html.find('span.timeago').timeago();
|
||||||
|
app.createUserTooltips();
|
||||||
|
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountPosts;
|
||||||
|
});
|
@ -0,0 +1,127 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, ajaxify, app, utils, socket, translator*/
|
||||||
|
|
||||||
|
define('forum/account/profile', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) {
|
||||||
|
var Account = {},
|
||||||
|
yourid,
|
||||||
|
theirid,
|
||||||
|
isFollowing;
|
||||||
|
|
||||||
|
Account.init = function() {
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
yourid = ajaxify.variables.get('yourid');
|
||||||
|
theirid = ajaxify.variables.get('theirid');
|
||||||
|
isFollowing = ajaxify.variables.get('isFollowing');
|
||||||
|
|
||||||
|
app.enterRoom('user/' + theirid);
|
||||||
|
|
||||||
|
processPage();
|
||||||
|
|
||||||
|
updateButtons();
|
||||||
|
|
||||||
|
$('#follow-btn').on('click', function() {
|
||||||
|
return toggleFollow('follow');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#unfollow-btn').on('click', function() {
|
||||||
|
return toggleFollow('unfollow');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chat-btn').on('click', function() {
|
||||||
|
app.openChat($('.account-username').html(), theirid);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.removeListener('event:user_status_change', onUserStatusChange);
|
||||||
|
socket.on('event:user_status_change', onUserStatusChange);
|
||||||
|
|
||||||
|
if (yourid !== theirid) {
|
||||||
|
socket.emit('user.increaseViewCount', theirid);
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.init(loadMoreTopics);
|
||||||
|
};
|
||||||
|
|
||||||
|
function processPage() {
|
||||||
|
$('.user-recent-posts img, .post-signature img').addClass('img-responsive');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtons() {
|
||||||
|
var isSelfOrNotLoggedIn = yourid === theirid || parseInt(yourid, 10) === 0;
|
||||||
|
$('#follow-btn').toggleClass('hide', isFollowing || isSelfOrNotLoggedIn);
|
||||||
|
$('#unfollow-btn').toggleClass('hide', !isFollowing || isSelfOrNotLoggedIn);
|
||||||
|
$('#chat-btn').toggleClass('hide', isSelfOrNotLoggedIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFollow(type) {
|
||||||
|
socket.emit('user.' + type, {
|
||||||
|
uid: theirid
|
||||||
|
}, function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#follow-btn').toggleClass('hide', type === 'follow');
|
||||||
|
$('#unfollow-btn').toggleClass('hide', type === 'unfollow');
|
||||||
|
app.alertSuccess('[[global:alert.' + type + ', ' + $('.account-username').html() + ']]');
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUserStatusChange(data) {
|
||||||
|
var onlineStatus = $('.account-online-status');
|
||||||
|
|
||||||
|
if(parseInt(ajaxify.variables.get('theirid'), 10) !== parseInt(data.uid, 10)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
translator.translate('[[global:' + data.status + ']]', function(translated) {
|
||||||
|
onlineStatus.attr('class', 'account-online-status fa fa-circle status ' + data.status)
|
||||||
|
.attr('title', translated)
|
||||||
|
.attr('data-original-title', translated);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMoreTopics(direction) {
|
||||||
|
if(direction < 0 || !$('.user-recent-posts').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.loading-indicator').removeClass('hidden');
|
||||||
|
|
||||||
|
infinitescroll.loadMore('user.loadMoreRecentPosts', {
|
||||||
|
after: $('.user-recent-posts').attr('data-nextstart'),
|
||||||
|
uid: theirid
|
||||||
|
}, function(data, done) {
|
||||||
|
if (data.posts && data.posts.length) {
|
||||||
|
onPostsLoaded(data.posts, done);
|
||||||
|
$('.user-recent-posts').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
$('.loading-indicator').addClass('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostsLoaded(posts, callback) {
|
||||||
|
posts = posts.filter(function(post) {
|
||||||
|
return !$('.user-recent-posts div[data-pid=' + post.pid + ']').length;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!posts.length) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.parseAndTranslate('account/profile', 'posts', {posts: posts}, function(html) {
|
||||||
|
|
||||||
|
$('.user-recent-posts .loading-indicator').before(html);
|
||||||
|
html.find('span.timeago').timeago();
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Account;
|
||||||
|
});
|
@ -0,0 +1,71 @@
|
|||||||
|
define('forum/account/settings', ['forum/account/header'], function(header) {
|
||||||
|
var AccountSettings = {};
|
||||||
|
|
||||||
|
AccountSettings.init = function() {
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
$('#submitBtn').on('click', function() {
|
||||||
|
var settings = {};
|
||||||
|
|
||||||
|
$('.account').find('input, textarea, select').each(function(id, input) {
|
||||||
|
input = $(input);
|
||||||
|
var setting = input.attr('data-property');
|
||||||
|
if (input.is('select')) {
|
||||||
|
settings[setting] = input.val();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (input.attr('type')) {
|
||||||
|
case 'text':
|
||||||
|
case 'textarea':
|
||||||
|
settings[setting] = input.val();
|
||||||
|
break;
|
||||||
|
case 'checkbox':
|
||||||
|
settings[setting] = input.is(':checked') ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('user.saveSettings', {uid: ajaxify.variables.get('theirid'), settings: settings}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('[[success:settings-saved]]');
|
||||||
|
app.loadConfig();
|
||||||
|
if (parseInt(app.uid, 10) === parseInt(ajaxify.variables.get('theirid'), 10)) {
|
||||||
|
ajaxify.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('user.getSettings', {uid: ajaxify.variables.get('theirid')}, function(err, settings) {
|
||||||
|
var inputs = $('.account').find('input, textarea, select');
|
||||||
|
|
||||||
|
inputs.each(function(index, input) {
|
||||||
|
input = $(input);
|
||||||
|
var setting = input.attr('data-property');
|
||||||
|
if (setting) {
|
||||||
|
if (input.is('select')) {
|
||||||
|
input.val(settings[setting]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (input.attr('type')) {
|
||||||
|
case 'text' :
|
||||||
|
case 'textarea' :
|
||||||
|
input.val(settings[setting]);
|
||||||
|
break;
|
||||||
|
case 'checkbox' :
|
||||||
|
input.prop('checked', !!settings[setting]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return AccountSettings;
|
||||||
|
});
|
@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, socket, utils */
|
||||||
|
|
||||||
|
define('forum/account/topics', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) {
|
||||||
|
var AccountTopics = {};
|
||||||
|
|
||||||
|
AccountTopics.init = function() {
|
||||||
|
header.init();
|
||||||
|
|
||||||
|
infinitescroll.init(loadMore);
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadMore(direction) {
|
||||||
|
if (direction < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('topics.loadMoreFromSet', {
|
||||||
|
set: 'uid:' + $('.account-username-box').attr('data-uid') + ':topics',
|
||||||
|
after: $('.user-topics').attr('data-nextstart')
|
||||||
|
}, function(data, done) {
|
||||||
|
|
||||||
|
if (data.topics && data.topics.length) {
|
||||||
|
onTopicsLoaded(data.topics, done);
|
||||||
|
$('.user-topics').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopicsLoaded(topics, callback) {
|
||||||
|
infinitescroll.parseAndTranslate('account/topics', 'topics', {topics: topics}, function(html) {
|
||||||
|
$('#topics-container').append(html);
|
||||||
|
html.find('span.timeago').timeago();
|
||||||
|
app.createUserTooltips();
|
||||||
|
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountTopics;
|
||||||
|
});
|
@ -0,0 +1,351 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, config, templates, app, utils, ajaxify, socket, translator */
|
||||||
|
|
||||||
|
define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll', 'share', 'navigator', 'forum/categoryTools'], function(composer, pagination, infinitescroll, share, navigator, categoryTools) {
|
||||||
|
var Category = {};
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.start', function(ev, data) {
|
||||||
|
if(data && data.url.indexOf('category') !== 0) {
|
||||||
|
navigator.hide();
|
||||||
|
|
||||||
|
removeListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function removeListeners() {
|
||||||
|
socket.removeListener('event:new_topic', Category.onNewTopic);
|
||||||
|
categoryTools.removeListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Category.init = function() {
|
||||||
|
var cid = ajaxify.variables.get('category_id');
|
||||||
|
|
||||||
|
app.enterRoom('category_' + cid);
|
||||||
|
|
||||||
|
share.addShareHandlers(ajaxify.variables.get('category_name'));
|
||||||
|
|
||||||
|
$('#new_post').on('click', function () {
|
||||||
|
composer.newTopic(cid);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('event:new_topic', Category.onNewTopic);
|
||||||
|
|
||||||
|
categoryTools.init(cid);
|
||||||
|
|
||||||
|
enableInfiniteLoadingOrPagination();
|
||||||
|
|
||||||
|
if (!config.usePagination) {
|
||||||
|
navigator.init('#topics-container > .category-item', ajaxify.variables.get('topic_count'), Category.toTop, Category.toBottom, Category.navigatorCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#topics-container').on('click', '.topic-title', function() {
|
||||||
|
var clickedTid = $(this).parents('li.category-item[data-tid]').attr('data-tid');
|
||||||
|
$('#topics-container li.category-item').each(function(index, el) {
|
||||||
|
if($(el).offset().top - $(window).scrollTop() > 0) {
|
||||||
|
localStorage.setItem('category:' + cid + ':bookmark', $(el).attr('data-tid'));
|
||||||
|
localStorage.setItem('category:' + cid + ':bookmark:clicked', clickedTid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
handleIgnoreWatch(cid);
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleIgnoreWatch(cid) {
|
||||||
|
$('.watch, .ignore').on('click', function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var command = $this.hasClass('watch') ? 'watch' : 'ignore';
|
||||||
|
|
||||||
|
socket.emit('categories.' + command, cid, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.watch').toggleClass('hidden', command === 'watch');
|
||||||
|
$('.ignore').toggleClass('hidden', command === 'ignore');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Category.toTop = function() {
|
||||||
|
navigator.scrollTop(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Category.toBottom = function() {
|
||||||
|
socket.emit('categories.lastTopicIndex', ajaxify.variables.get('category_id'), function(err, index) {
|
||||||
|
navigator.scrollBottom(index);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Category.navigatorCallback = function(element, elementCount) {
|
||||||
|
return parseInt(element.attr('data-index'), 10) + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
$(window).on('action:popstate', function(ev, data) {
|
||||||
|
if(data.url.indexOf('category/') === 0) {
|
||||||
|
var cid = data.url.match(/^category\/(\d+)/);
|
||||||
|
if (cid && cid[1]) {
|
||||||
|
cid = cid[1];
|
||||||
|
}
|
||||||
|
if (!cid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bookmark = localStorage.getItem('category:' + cid + ':bookmark');
|
||||||
|
var clicked = localStorage.getItem('category:' + cid + ':bookmark:clicked');
|
||||||
|
|
||||||
|
if (!bookmark) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.usePagination) {
|
||||||
|
socket.emit('topics.getTidPage', bookmark, function(err, page) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(parseInt(page, 10) !== pagination.currentPage) {
|
||||||
|
pagination.loadPage(page);
|
||||||
|
} else {
|
||||||
|
Category.scrollToTopic(bookmark, clicked, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.emit('topics.getTidIndex', bookmark, function(err, index) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
Category.highlightTopic(clicked);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#topics-container').empty();
|
||||||
|
|
||||||
|
loadTopicsAfter(index, function() {
|
||||||
|
Category.scrollToTopic(bookmark, clicked, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Category.highlightTopic = function(tid) {
|
||||||
|
var highlight = $('#topics-container li.category-item[data-tid="' + tid + '"]');
|
||||||
|
if(highlight.length && !highlight.hasClass('highlight')) {
|
||||||
|
highlight.addClass('highlight');
|
||||||
|
setTimeout(function() {
|
||||||
|
highlight.removeClass('highlight');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Category.scrollToTopic = function(tid, clickedTid, duration, offset) {
|
||||||
|
if(!tid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!offset) {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($('#topics-container li.category-item[data-tid="' + tid + '"]').length) {
|
||||||
|
var cid = ajaxify.variables.get('category_id');
|
||||||
|
var scrollTo = $('#topics-container li.category-item[data-tid="' + tid + '"]');
|
||||||
|
|
||||||
|
if (cid && scrollTo.length) {
|
||||||
|
$('html, body').animate({
|
||||||
|
scrollTop: (scrollTo.offset().top - $('#header-menu').height() - offset) + 'px'
|
||||||
|
}, duration !== undefined ? duration : 400, function() {
|
||||||
|
Category.highlightTopic(clickedTid);
|
||||||
|
navigator.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function enableInfiniteLoadingOrPagination() {
|
||||||
|
if (!config.usePagination) {
|
||||||
|
infinitescroll.init(Category.loadMoreTopics);
|
||||||
|
} else {
|
||||||
|
navigator.hide();
|
||||||
|
pagination.init(ajaxify.variables.get('currentPage'), ajaxify.variables.get('pageCount'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Category.onNewTopic = function(topic) {
|
||||||
|
$(window).trigger('filter:categories.new_topic', topic);
|
||||||
|
|
||||||
|
ajaxify.loadTemplate('category', function(categoryTemplate) {
|
||||||
|
var html = templates.parse(templates.getBlock(categoryTemplate, 'topics'), {
|
||||||
|
privileges: {editable: !!$('.thread-tools').length},
|
||||||
|
topics: [topic]
|
||||||
|
});
|
||||||
|
|
||||||
|
translator.translate(html, function(translatedHTML) {
|
||||||
|
var topic = $(translatedHTML),
|
||||||
|
container = $('#topics-container'),
|
||||||
|
topics = $('#topics-container').children('.category-item'),
|
||||||
|
numTopics = topics.length;
|
||||||
|
|
||||||
|
$('#topics-container, .category-sidebar').removeClass('hidden');
|
||||||
|
|
||||||
|
var noTopicsWarning = $('#category-no-topics');
|
||||||
|
if (noTopicsWarning.length) {
|
||||||
|
noTopicsWarning.remove();
|
||||||
|
ajaxify.widgets.render('category', window.location.pathname.slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numTopics > 0) {
|
||||||
|
for (var x = 0; x < numTopics; x++) {
|
||||||
|
var pinned = $(topics[x]).hasClass('pinned');
|
||||||
|
if (pinned) {
|
||||||
|
if(x === numTopics - 1) {
|
||||||
|
topic.insertAfter(topics[x]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
topic.insertBefore(topics[x]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
container.append(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
topic.hide().fadeIn('slow');
|
||||||
|
|
||||||
|
socket.emit('categories.getPageCount', ajaxify.variables.get('category_id'), function(err, newPageCount) {
|
||||||
|
pagination.recreatePaginationLinks(newPageCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
topic.find('span.timeago').timeago();
|
||||||
|
app.createUserTooltips();
|
||||||
|
updateTopicCount();
|
||||||
|
|
||||||
|
$(window).trigger('action:categories.new_topic.loaded');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateTopicCount() {
|
||||||
|
socket.emit('categories.getTopicCount', ajaxify.variables.get('category_id'), function(err, topicCount) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
navigator.setCount(topicCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Category.onTopicsLoaded = function(data, callback) {
|
||||||
|
if(!data || !data.topics.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAlreadyAddedTopics(topics) {
|
||||||
|
return topics.filter(function(topic) {
|
||||||
|
return $('#topics-container li[data-tid="' + topic.tid +'"]').length === 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var after = null,
|
||||||
|
before = null;
|
||||||
|
|
||||||
|
function findInsertionPoint() {
|
||||||
|
if (!$('#topics-container .category-item[data-tid]').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var last = $('#topics-container .category-item[data-tid]').last();
|
||||||
|
var lastIndex = last.attr('data-index');
|
||||||
|
var firstIndex = data.topics[data.topics.length - 1].index;
|
||||||
|
if (firstIndex > lastIndex) {
|
||||||
|
after = last;
|
||||||
|
} else {
|
||||||
|
before = $('#topics-container .category-item[data-tid]').first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.topics = removeAlreadyAddedTopics(data.topics);
|
||||||
|
if(!data.topics.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
findInsertionPoint();
|
||||||
|
|
||||||
|
ajaxify.loadTemplate('category', function(categoryTemplate) {
|
||||||
|
var html = templates.parse(templates.getBlock(categoryTemplate, 'topics'), data);
|
||||||
|
|
||||||
|
translator.translate(html, function(translatedHTML) {
|
||||||
|
var container = $('#topics-container'),
|
||||||
|
html = $(translatedHTML);
|
||||||
|
|
||||||
|
$('#topics-container, .category-sidebar').removeClass('hidden');
|
||||||
|
$('#category-no-topics').remove();
|
||||||
|
|
||||||
|
if(config.usePagination) {
|
||||||
|
container.empty().append(html);
|
||||||
|
} else {
|
||||||
|
if(after) {
|
||||||
|
html.insertAfter(after);
|
||||||
|
} else if(before) {
|
||||||
|
html.insertBefore(before);
|
||||||
|
} else {
|
||||||
|
container.append(html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
html.find('span.timeago').timeago();
|
||||||
|
app.createUserTooltips();
|
||||||
|
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Category.loadMoreTopics = function(direction) {
|
||||||
|
if (!$('#topics-container').length || !$('#topics-container').children().length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.calculateAfter(direction, '#topics-container .category-item[data-tid]', config.topicsPerPage, false, function(after, offset, el) {
|
||||||
|
loadTopicsAfter(after, function() {
|
||||||
|
if (direction < 0 && el) {
|
||||||
|
Category.scrollToTopic(el.attr('data-tid'), null, 0, offset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadTopicsAfter(after, callback) {
|
||||||
|
if(!utils.isNumber(after) || (after === 0 && $('#topics-container li.category-item[data-index="0"]').length)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).trigger('action:categories.loading');
|
||||||
|
infinitescroll.loadMore('categories.loadMore', {
|
||||||
|
cid: ajaxify.variables.get('category_id'),
|
||||||
|
after: after
|
||||||
|
}, function (data, done) {
|
||||||
|
|
||||||
|
if (data.topics && data.topics.length) {
|
||||||
|
Category.onTopicsLoaded(data, function() {
|
||||||
|
done();
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
$('#topics-container').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).trigger('action:categories.loaded');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Category;
|
||||||
|
});
|
@ -0,0 +1,205 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, translator, socket, bootbox, ajaxify */
|
||||||
|
|
||||||
|
|
||||||
|
define('forum/categoryTools', ['forum/topic/move', 'topicSelect'], function(move, topicSelect) {
|
||||||
|
|
||||||
|
var CategoryTools = {};
|
||||||
|
|
||||||
|
CategoryTools.init = function(cid) {
|
||||||
|
CategoryTools.cid = cid;
|
||||||
|
|
||||||
|
topicSelect.init(onTopicSelect);
|
||||||
|
|
||||||
|
$('.delete_thread').on('click', function(e) {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
categoryCommand(isAny(isTopicDeleted, tids) ? 'restore' : 'delete', tids);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.purge_thread').on('click', function() {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
categoryCommand('purge', tids);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.lock_thread').on('click', function() {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
if (tids.length) {
|
||||||
|
socket.emit(isAny(isTopicLocked, tids) ? 'topics.unlock' : 'topics.lock', {tids: tids, cid: CategoryTools.cid}, onCommandComplete);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.pin_thread').on('click', function() {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
if (tids.length) {
|
||||||
|
socket.emit(isAny(isTopicPinned, tids) ? 'topics.unpin' : 'topics.pin', {tids: tids, cid: CategoryTools.cid}, onCommandComplete);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.markAsUnreadForAll').on('click', function() {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
if (tids.length) {
|
||||||
|
socket.emit('topics.markAsUnreadForAll', tids, function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
|
||||||
|
|
||||||
|
onCommandComplete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.move_thread').on('click', function() {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
|
||||||
|
if (tids.length) {
|
||||||
|
move.init(tids, cid, onCommandComplete);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.move_all_threads').on('click', function() {
|
||||||
|
move.init(null, cid, function(err) {
|
||||||
|
ajaxify.refresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
socket.on('event:topic_deleted', setDeleteState);
|
||||||
|
socket.on('event:topic_restored', setDeleteState);
|
||||||
|
socket.on('event:topic_purged', onTopicPurged);
|
||||||
|
socket.on('event:topic_locked', setLockedState);
|
||||||
|
socket.on('event:topic_unlocked', setLockedState);
|
||||||
|
socket.on('event:topic_pinned', setPinnedState);
|
||||||
|
socket.on('event:topic_unpinned', setPinnedState);
|
||||||
|
socket.on('event:topic_moved', onTopicMoved);
|
||||||
|
};
|
||||||
|
|
||||||
|
function categoryCommand(command, tids) {
|
||||||
|
if (!tids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) {
|
||||||
|
bootbox.confirm(msg, function(confirm) {
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('topics.' + command, {tids: tids, cid: CategoryTools.cid}, onCommandComplete);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CategoryTools.removeListeners = function() {
|
||||||
|
socket.removeListener('event:topic_deleted', setDeleteState);
|
||||||
|
socket.removeListener('event:topic_restored', setDeleteState);
|
||||||
|
socket.removeListener('event:topic_purged', onTopicPurged);
|
||||||
|
socket.removeListener('event:topic_locked', setLockedState);
|
||||||
|
socket.removeListener('event:topic_unlocked', setLockedState);
|
||||||
|
socket.removeListener('event:topic_pinned', setPinnedState);
|
||||||
|
socket.removeListener('event:topic_unpinned', setPinnedState);
|
||||||
|
socket.removeListener('event:topic_moved', onTopicMoved);
|
||||||
|
};
|
||||||
|
|
||||||
|
function closeDropDown() {
|
||||||
|
$('.thread-tools.open').find('.dropdown-toggle').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCommandComplete(err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
closeDropDown();
|
||||||
|
topicSelect.unselectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopicSelect() {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
var isAnyDeleted = isAny(isTopicDeleted, tids);
|
||||||
|
var areAllDeleted = areAll(isTopicDeleted, tids);
|
||||||
|
var isAnyPinned = isAny(isTopicPinned, tids);
|
||||||
|
var isAnyLocked = isAny(isTopicLocked, tids);
|
||||||
|
|
||||||
|
$('.delete_thread span').translateHtml('<i class="fa fa-fw ' + (isAnyDeleted ? 'fa-history' : 'fa-trash-o') + '"></i> [[topic:thread_tools.' + (isAnyDeleted ? 'restore' : 'delete') + ']]');
|
||||||
|
$('.pin_thread').translateHtml('<i class="fa fa-fw fa-thumb-tack"></i> [[topic:thread_tools.' + (isAnyPinned ? 'unpin' : 'pin') + ']]');
|
||||||
|
$('.lock_thread').translateHtml('<i class="fa fa-fw fa-' + (isAnyLocked ? 'un': '') + 'lock"></i> [[topic:thread_tools.' + (isAnyLocked ? 'un': '') + 'lock]]');
|
||||||
|
$('.purge_thread').toggleClass('hidden', !areAllDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAny(method, tids) {
|
||||||
|
for(var i=0; i<tids.length; ++i) {
|
||||||
|
if(method(tids[i])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function areAll(method, tids) {
|
||||||
|
for(var i=0; i<tids.length; ++i) {
|
||||||
|
if(!method(tids[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTopicDeleted(tid) {
|
||||||
|
return getTopicEl(tid).hasClass('deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTopicLocked(tid) {
|
||||||
|
return getTopicEl(tid).hasClass('locked');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTopicPinned(tid) {
|
||||||
|
return getTopicEl(tid).hasClass('pinned');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTopicEl(tid) {
|
||||||
|
return $('#topics-container li[data-tid="' + tid + '"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDeleteState(data) {
|
||||||
|
var topic = getTopicEl(data.tid);
|
||||||
|
topic.toggleClass('deleted', data.isDeleted);
|
||||||
|
topic.find('.fa-lock').toggleClass('hide', !data.isDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPinnedState(data) {
|
||||||
|
var topic = getTopicEl(data.tid);
|
||||||
|
topic.toggleClass('pinned', data.isPinned);
|
||||||
|
topic.find('.fa-thumb-tack').toggleClass('hide', !data.isPinned);
|
||||||
|
ajaxify.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLockedState(data) {
|
||||||
|
var topic = getTopicEl(data.tid);
|
||||||
|
topic.toggleClass('locked', data.isLocked);
|
||||||
|
topic.find('.fa-lock').toggleClass('hide', !data.isLocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopicMoved(data) {
|
||||||
|
getTopicEl(data.tid).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopicPurged(tids) {
|
||||||
|
if (!tids) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(var i=0; i<tids.length; ++i) {
|
||||||
|
getTopicEl(tids[i]).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CategoryTools;
|
||||||
|
});
|
@ -0,0 +1,275 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, ajaxify, utils, socket, templates */
|
||||||
|
|
||||||
|
define('forum/chats', ['string', 'sounds', 'forum/infinitescroll'], function(S, sounds, infinitescroll) {
|
||||||
|
var Chats = {
|
||||||
|
initialised: false
|
||||||
|
};
|
||||||
|
|
||||||
|
var newMessage = false;
|
||||||
|
|
||||||
|
Chats.init = function() {
|
||||||
|
var containerEl = $('.expanded-chat ul');
|
||||||
|
|
||||||
|
if (!Chats.initialised) {
|
||||||
|
Chats.addSocketListeners();
|
||||||
|
Chats.addGlobalEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Chats.addEventListeners();
|
||||||
|
Chats.resizeMainWindow();
|
||||||
|
Chats.scrollToBottom(containerEl);
|
||||||
|
Chats.setActive();
|
||||||
|
|
||||||
|
Chats.initialised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.getRecipientUid = function() {
|
||||||
|
return parseInt($('.expanded-chat').attr('data-uid'), 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.isCurrentChat = function(uid) {
|
||||||
|
return Chats.getRecipientUid() === parseInt(uid, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.addEventListeners = function() {
|
||||||
|
var inputEl = $('.chat-input'),
|
||||||
|
sendEl = $('.expanded-chat button[data-action="send"]'),
|
||||||
|
popoutEl = $('[data-action="pop-out"]');
|
||||||
|
|
||||||
|
$('.chats-list').on('click', 'li', function(e) {
|
||||||
|
ajaxify.go('chats/' + utils.slugify($(this).attr('data-username')));
|
||||||
|
});
|
||||||
|
|
||||||
|
inputEl.on('keypress', function(e) {
|
||||||
|
if(e.which === 13) {
|
||||||
|
Chats.sendMessage(Chats.getRecipientUid(), inputEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inputEl.on('keyup', function() {
|
||||||
|
var val = !!$(this).val();
|
||||||
|
if ((val && $(this).attr('data-typing') === 'true') || (!val && $(this).attr('data-typing') === 'false')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chats.notifyTyping(Chats.getRecipientUid(), val);
|
||||||
|
$(this).attr('data-typing', val);
|
||||||
|
});
|
||||||
|
|
||||||
|
sendEl.on('click', function(e) {
|
||||||
|
Chats.sendMessage(Chats.getRecipientUid(), inputEl);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
popoutEl.on('click', function() {
|
||||||
|
var username = $('.expanded-chat').attr('data-username'),
|
||||||
|
uid = Chats.getRecipientUid();
|
||||||
|
|
||||||
|
if (app.previousUrl && app.previousUrl.match(/chats/)) {
|
||||||
|
ajaxify.go('chats', function() {
|
||||||
|
app.openChat(username, uid);
|
||||||
|
}, true);
|
||||||
|
} else {
|
||||||
|
window.history.go(-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.recent-chats').on('scroll', function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var bottom = ($this[0].scrollHeight - $this.height()) * 0.9;
|
||||||
|
if ($this.scrollTop() > bottom) {
|
||||||
|
loadMoreRecentChats();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.expanded-chat [data-since]').on('click', function() {
|
||||||
|
var since = $(this).attr('data-since');
|
||||||
|
$('.expanded-chat [data-since]').removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
loadChatSince(since);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadChatSince(since) {
|
||||||
|
var uid = Chats.getRecipientUid();
|
||||||
|
if (!uid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.emit('modules.chats.get', {touid: uid, since: since}, function(err, messages) {
|
||||||
|
var chatContent = $('.expanded-chat .chat-content');
|
||||||
|
chatContent.find('.chat-message').remove();
|
||||||
|
Chats.parseMessage(messages, onMessagesParsed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Chats.addGlobalEventListeners = function() {
|
||||||
|
$(window).on('resize', Chats.resizeMainWindow);
|
||||||
|
$(window).on('mousemove keypress click', function() {
|
||||||
|
if (newMessage) {
|
||||||
|
var recipientUid = Chats.getRecipientUid();
|
||||||
|
if (recipientUid) {
|
||||||
|
socket.emit('modules.chats.markRead', recipientUid);
|
||||||
|
newMessage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function onMessagesParsed(html) {
|
||||||
|
var newMessage = $(html);
|
||||||
|
newMessage.insertBefore($('.user-typing'));
|
||||||
|
newMessage.find('span.timeago').timeago();
|
||||||
|
newMessage.find('img:not(".chat-user-image")').addClass('img-responsive');
|
||||||
|
Chats.scrollToBottom($('.expanded-chat .chat-content'));
|
||||||
|
}
|
||||||
|
|
||||||
|
Chats.addSocketListeners = function() {
|
||||||
|
socket.on('event:chats.receive', function(data) {
|
||||||
|
var typingNotifEl = $('.user-typing'),
|
||||||
|
containerEl = $('.expanded-chat ul');
|
||||||
|
|
||||||
|
if (Chats.isCurrentChat(data.withUid)) {
|
||||||
|
newMessage = data.self === 0;
|
||||||
|
data.message.self = data.self;
|
||||||
|
Chats.parseMessage(data.message, onMessagesParsed);
|
||||||
|
} else {
|
||||||
|
$('.chats-list li[data-uid="' + data.withUid + '"]').addClass('unread');
|
||||||
|
app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + data.message.fromUser.username + ']]');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('event:chats.userStartTyping', function(withUid) {
|
||||||
|
var typingNotifEl = $('.user-typing');
|
||||||
|
|
||||||
|
if (Chats.isCurrentChat(withUid)) {
|
||||||
|
typingNotifEl.removeClass('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.chats-list li[data-uid="' + withUid + '"]').addClass('typing');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('event:chats.userStopTyping', function(withUid) {
|
||||||
|
var typingNotifEl = $('.user-typing');
|
||||||
|
|
||||||
|
if (Chats.isCurrentChat(withUid)) {
|
||||||
|
typingNotifEl.addClass('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.chats-list li[data-uid="' + withUid + '"]').removeClass('typing');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('event:user_status_change', function(data) {
|
||||||
|
var userEl = $('.chats-list li[data-uid="' + data.uid +'"]');
|
||||||
|
|
||||||
|
if (userEl.length) {
|
||||||
|
var statusEl = userEl.find('.status');
|
||||||
|
translator.translate('[[global:' + data.status + ']]', function(translated) {
|
||||||
|
statusEl.attr('class', 'fa fa-circle status ' + data.status)
|
||||||
|
.attr('title', translated)
|
||||||
|
.attr('data-original-title', translated);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.resizeMainWindow = function() {
|
||||||
|
var messagesList = $('.expanded-chat ul');
|
||||||
|
|
||||||
|
if (messagesList.length) {
|
||||||
|
var margin = $('.expanded-chat ul').outerHeight(true) - $('.expanded-chat ul').height(),
|
||||||
|
inputHeight = $('.chat-input').outerHeight(true),
|
||||||
|
fromTop = messagesList.offset().top;
|
||||||
|
|
||||||
|
messagesList.height($(window).height() - (fromTop + inputHeight + (margin * 4)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.notifyTyping = function(toUid, typing) {
|
||||||
|
socket.emit('modules.chats.user' + (typing ? 'Start' : 'Stop') + 'Typing', {
|
||||||
|
touid: toUid,
|
||||||
|
fromUid: app.uid
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.sendMessage = function(toUid, inputEl) {
|
||||||
|
var msg = S(inputEl.val()).stripTags().s;
|
||||||
|
if (msg.length) {
|
||||||
|
msg = msg +'\n';
|
||||||
|
socket.emit('modules.chats.send', {
|
||||||
|
touid:toUid,
|
||||||
|
message:msg
|
||||||
|
});
|
||||||
|
inputEl.val('');
|
||||||
|
sounds.play('chat-outgoing');
|
||||||
|
Chats.notifyTyping(toUid, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.scrollToBottom = function(containerEl) {
|
||||||
|
if (containerEl.length) {
|
||||||
|
containerEl.scrollTop(
|
||||||
|
containerEl[0].scrollHeight - containerEl.height()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.setActive = function() {
|
||||||
|
var recipientUid = Chats.getRecipientUid();
|
||||||
|
if (recipientUid) {
|
||||||
|
socket.emit('modules.chats.markRead', recipientUid);
|
||||||
|
$('.expanded-chat input').focus();
|
||||||
|
}
|
||||||
|
$('.chats-list li').removeClass('bg-primary');
|
||||||
|
$('.chats-list li[data-uid="' + recipientUid + '"]').addClass('bg-primary');
|
||||||
|
};
|
||||||
|
|
||||||
|
Chats.parseMessage = function(data, callback) {
|
||||||
|
templates.parse('partials/chat_message' + (Array.isArray(data) ? 's' : ''), {
|
||||||
|
messages: data
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadMoreRecentChats() {
|
||||||
|
var recentChats = $('.recent-chats');
|
||||||
|
if (recentChats.attr('loading')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recentChats.attr('loading', 1);
|
||||||
|
socket.emit('modules.chats.getRecentChats', {
|
||||||
|
after: recentChats.attr('data-nextstart')
|
||||||
|
}, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data.users.length) {
|
||||||
|
onRecentChatsLoaded(data.users, function() {
|
||||||
|
recentChats.removeAttr('loading');
|
||||||
|
recentChats.attr('data-nextstart', data.nextStart);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
recentChats.removeAttr('loading');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRecentChatsLoaded(users, callback) {
|
||||||
|
users = users.filter(function(user) {
|
||||||
|
return !$('.recent-chats li[data-uid=' + user.uid + ']').length;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!users.length) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.parseAndTranslate('chats', 'chats', {chats: users}, function(html) {
|
||||||
|
$('.recent-chats').append(html);
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Chats;
|
||||||
|
});
|
@ -0,0 +1,69 @@
|
|||||||
|
define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) {
|
||||||
|
|
||||||
|
Notifications.prepareDOM();
|
||||||
|
Chat.prepareDOM();
|
||||||
|
translator.prepareDOM();
|
||||||
|
|
||||||
|
function updateUnreadTopicCount(err, count) {
|
||||||
|
if (err) {
|
||||||
|
return console.warn('Error updating unread count', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#unread-count')
|
||||||
|
.toggleClass('unread-count', count > 0)
|
||||||
|
.attr('data-content', count > 20 ? '20+' : count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUnreadChatCount(err, count) {
|
||||||
|
if (err) {
|
||||||
|
return console.warn('Error updating unread count', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#chat-count')
|
||||||
|
.toggleClass('unread-count', count > 0)
|
||||||
|
.attr('data-content', count > 20 ? '20+' : count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initUnreadTopics() {
|
||||||
|
var unreadTopics = {};
|
||||||
|
|
||||||
|
function onNewPost(data) {
|
||||||
|
if (data && data.posts && data.posts.length) {
|
||||||
|
var post = data.posts[0];
|
||||||
|
|
||||||
|
if (parseInt(post.uid, 10) !== parseInt(app.uid, 10) && !unreadTopics[post.topic.tid]) {
|
||||||
|
increaseUnreadCount();
|
||||||
|
markTopicsUnread(post.topic.tid);
|
||||||
|
unreadTopics[post.topic.tid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function increaseUnreadCount() {
|
||||||
|
var count = parseInt($('#unread-count').attr('data-content'), 10) + 1;
|
||||||
|
updateUnreadTopicCount(null, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function markTopicsUnread(tid) {
|
||||||
|
$('[data-tid="' + tid + '"]').addClass('unread');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.end', function(ev, data) {
|
||||||
|
var tid = data.url.match(/^topic\/(\d+)/);
|
||||||
|
|
||||||
|
if (tid && tid[1]) {
|
||||||
|
delete unreadTopics[tid[1]];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('event:new_post', onNewPost);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('event:unread.updateCount', updateUnreadTopicCount);
|
||||||
|
socket.emit('user.getUnreadCount', updateUnreadTopicCount);
|
||||||
|
|
||||||
|
socket.on('event:unread.updateChatCount', updateUnreadChatCount);
|
||||||
|
socket.emit('user.getUnreadChatCount', updateUnreadChatCount);
|
||||||
|
|
||||||
|
initUnreadTopics();
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
define('forum/groups/details', function() {
|
||||||
|
var Details = {};
|
||||||
|
|
||||||
|
Details.init = function() {
|
||||||
|
var memberListEl = $('.groups.details .members');
|
||||||
|
|
||||||
|
memberListEl.on('click', '[data-slug]', function() {
|
||||||
|
var slug = this.getAttribute('data-slug');
|
||||||
|
ajaxify.go('user/' + slug);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Details;
|
||||||
|
});
|
@ -0,0 +1,77 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, socket, app, templates, translator, ajaxify*/
|
||||||
|
|
||||||
|
define('forum/home', function() {
|
||||||
|
var home = {};
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.start', function(ev, data) {
|
||||||
|
if (data.url !== '') {
|
||||||
|
socket.removeListener('event:new_post', home.onNewPost);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
home.init = function() {
|
||||||
|
app.enterRoom('home');
|
||||||
|
|
||||||
|
socket.removeListener('event:new_post', home.onNewPost);
|
||||||
|
socket.on('event:new_post', home.onNewPost);
|
||||||
|
|
||||||
|
$('.home .category-header').tooltip({
|
||||||
|
placement: 'bottom'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
home.onNewPost = function(data) {
|
||||||
|
if (data && data.posts && data.posts.length && data.posts[0].topic) {
|
||||||
|
renderNewPost(data.posts[0].topic.cid, data.posts[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderNewPost(cid, post) {
|
||||||
|
var category = $('.home .category-item[data-cid="' + cid + '"]');
|
||||||
|
var categoryBox = category.find('.category-box');
|
||||||
|
var numRecentReplies = category.attr('data-numRecentReplies');
|
||||||
|
if (!numRecentReplies || !parseInt(numRecentReplies, 10)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var recentPosts = categoryBox.find('.post-preview');
|
||||||
|
var insertBefore = recentPosts.first();
|
||||||
|
|
||||||
|
parseAndTranslate([post], function(html) {
|
||||||
|
html.hide();
|
||||||
|
if(recentPosts.length === 0) {
|
||||||
|
html.appendTo(categoryBox);
|
||||||
|
} else {
|
||||||
|
html.insertBefore(recentPosts.first());
|
||||||
|
}
|
||||||
|
|
||||||
|
html.fadeIn();
|
||||||
|
|
||||||
|
app.createUserTooltips();
|
||||||
|
|
||||||
|
if (categoryBox.find('.post-preview').length > parseInt(numRecentReplies, 10)) {
|
||||||
|
recentPosts.last().remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).trigger('action:posts.loaded');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAndTranslate(posts, callback) {
|
||||||
|
ajaxify.loadTemplate('home', function(homeTemplate) {
|
||||||
|
var html = templates.parse(templates.getBlock(homeTemplate, 'posts'), {categories: {posts: posts}});
|
||||||
|
|
||||||
|
translator.translate(html, function(translatedHTML) {
|
||||||
|
translatedHTML = $(translatedHTML);
|
||||||
|
translatedHTML.find('img').addClass('img-responsive');
|
||||||
|
translatedHTML.find('span.timeago').timeago();
|
||||||
|
callback(translatedHTML);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return home;
|
||||||
|
});
|
@ -0,0 +1,92 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, socket, ajaxify, translator, templates, app */
|
||||||
|
|
||||||
|
define('forum/infinitescroll', function() {
|
||||||
|
|
||||||
|
var scroll = {};
|
||||||
|
var callback;
|
||||||
|
var previousScrollTop = 0;
|
||||||
|
var loadingMore = false;
|
||||||
|
var topOffset = 0;
|
||||||
|
|
||||||
|
scroll.init = function(cb, _topOffest) {
|
||||||
|
callback = cb;
|
||||||
|
topOffset = _topOffest || 0;
|
||||||
|
$(window).off('scroll', onScroll).on('scroll', onScroll);
|
||||||
|
|
||||||
|
// if ($(document).height() === $(window).height()) {
|
||||||
|
// callback(1);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
function onScroll() {
|
||||||
|
var originalPostEl = $('li[data-index="0"]'),
|
||||||
|
top = $(window).height() * 0.15 + topOffset + (originalPostEl ? originalPostEl.outerHeight() : 0),
|
||||||
|
bottom = ($(document).height() - $(window).height()) * 0.85,
|
||||||
|
currentScrollTop = $(window).scrollTop();
|
||||||
|
|
||||||
|
if(currentScrollTop < top && currentScrollTop < previousScrollTop) {
|
||||||
|
callback(-1);
|
||||||
|
} else if (currentScrollTop > bottom && currentScrollTop > previousScrollTop) {
|
||||||
|
callback(1);
|
||||||
|
}
|
||||||
|
previousScrollTop = currentScrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll.loadMore = function(method, data, callback) {
|
||||||
|
if (loadingMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadingMore = true;
|
||||||
|
socket.emit(method, data, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
loadingMore = false;
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
callback(data, function() {
|
||||||
|
loadingMore = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
scroll.parseAndTranslate = function(template, blockName, data, callback) {
|
||||||
|
ajaxify.loadTemplate(template, function(templateHtml) {
|
||||||
|
var html = templates.parse(templates.getBlock(templateHtml, blockName), data);
|
||||||
|
|
||||||
|
translator.translate(html, function(translatedHTML) {
|
||||||
|
callback($(translatedHTML));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
scroll.calculateAfter = function(direction, selector, count, reverse, callback) {
|
||||||
|
var after = 0,
|
||||||
|
offset = 0,
|
||||||
|
el = direction > 0 ? $(selector).last() : $(selector).first(),
|
||||||
|
increment;
|
||||||
|
|
||||||
|
count = reverse ? -count : count;
|
||||||
|
increment = reverse ? -1 : 1;
|
||||||
|
|
||||||
|
if (direction > 0) {
|
||||||
|
after = parseInt(el.attr('data-index'), 10) + increment;
|
||||||
|
} else {
|
||||||
|
after = parseInt(el.attr('data-index'), 10);
|
||||||
|
if (isNaN(after)) {
|
||||||
|
after = 0;
|
||||||
|
}
|
||||||
|
after -= count;
|
||||||
|
if (after < 0) {
|
||||||
|
after = 0;
|
||||||
|
}
|
||||||
|
if (el && el.offset()) {
|
||||||
|
offset = el.offset().top - $('#header-menu').offset().top + $('#header-menu').height();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(after, offset, el);
|
||||||
|
};
|
||||||
|
|
||||||
|
return scroll;
|
||||||
|
});
|
@ -0,0 +1,39 @@
|
|||||||
|
"use strict";
|
||||||
|
/* global define, app, RELATIVE_PATH */
|
||||||
|
|
||||||
|
define('forum/login', function() {
|
||||||
|
var Login = {};
|
||||||
|
|
||||||
|
Login.init = function() {
|
||||||
|
var errorEl = $('#login-error-notify'),
|
||||||
|
submitEl = $('#login'),
|
||||||
|
formEl = $('#login-form');
|
||||||
|
|
||||||
|
submitEl.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!$('#username').val() || !$('#password').val()) {
|
||||||
|
translator.translate('[[error:invalid-username-or-password]]', function(translated) {
|
||||||
|
errorEl.find('p').text(translated)
|
||||||
|
errorEl.show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorEl.hide();
|
||||||
|
|
||||||
|
if (!submitEl.hasClass('disabled')) {
|
||||||
|
submitEl.addClass('disabled');
|
||||||
|
formEl.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#login-error-notify button').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
errorEl.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#content #username').focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
return Login;
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
define('forum/notifications', function() {
|
||||||
|
var Notifications = {};
|
||||||
|
|
||||||
|
Notifications.init = function() {
|
||||||
|
var listEl = $('.notifications-list');
|
||||||
|
|
||||||
|
$('span.timeago').timeago();
|
||||||
|
|
||||||
|
// Allow the user to click anywhere in the LI
|
||||||
|
listEl.on('click', 'li', function(e) {
|
||||||
|
this.querySelector('a').click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Notifications;
|
||||||
|
});
|
@ -0,0 +1,108 @@
|
|||||||
|
'use strict';
|
||||||
|
/*global define, utils, ajaxify, bootbox*/
|
||||||
|
|
||||||
|
define('forum/pagination', function() {
|
||||||
|
var pagination = {};
|
||||||
|
|
||||||
|
pagination.currentPage = 0;
|
||||||
|
pagination.pageCount = 0;
|
||||||
|
|
||||||
|
pagination.init = function(currentPage, pageCount) {
|
||||||
|
pagination.currentPage = parseInt(currentPage, 10);
|
||||||
|
pagination.pageCount = parseInt(pageCount, 10);
|
||||||
|
|
||||||
|
pagination.recreatePaginationLinks(pageCount);
|
||||||
|
|
||||||
|
$('.pagination')
|
||||||
|
.on('click', '.previous', function() {
|
||||||
|
return pagination.loadPage(pagination.currentPage - 1);
|
||||||
|
}).on('click', '.next', function() {
|
||||||
|
return pagination.loadPage(pagination.currentPage + 1);
|
||||||
|
}).on('click', '.select_page', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
bootbox.prompt('Enter page number:', function(pageNum) {
|
||||||
|
pagination.loadPage(pageNum);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
pagination.recreatePaginationLinks = function(newPageCount) {
|
||||||
|
pagination.pageCount = parseInt(newPageCount, 10);
|
||||||
|
|
||||||
|
var pagesToShow = determinePagesToShow();
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
for(var i=0; i<pagesToShow.length; ++i) {
|
||||||
|
if(i > 0) {
|
||||||
|
if (pagesToShow[i] - 1 !== pagesToShow[i-1]) {
|
||||||
|
html += '<li><a class="select_page" href="#">|</a></li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html += '<li class="page" data-page="' + pagesToShow[i] + '"><a href="#">' + pagesToShow[i] + '</a></li>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.pagination li.page').remove();
|
||||||
|
$('.pagination li .select_page').parent().remove();
|
||||||
|
$(html).insertAfter($('.pagination li.previous'));
|
||||||
|
|
||||||
|
updatePageLinks();
|
||||||
|
};
|
||||||
|
|
||||||
|
function determinePagesToShow() {
|
||||||
|
var pagesToShow = [1];
|
||||||
|
if(pagination.pageCount !== 1) {
|
||||||
|
pagesToShow.push(pagination.pageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
var previous = pagination.currentPage - 1;
|
||||||
|
var next = pagination.currentPage + 1;
|
||||||
|
|
||||||
|
if(previous > 1 && pagesToShow.indexOf(previous) === -1) {
|
||||||
|
pagesToShow.push(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(next < pagination.pageCount && pagesToShow.indexOf(next) === -1) {
|
||||||
|
pagesToShow.push(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pagesToShow.indexOf(pagination.currentPage) === -1) {
|
||||||
|
pagesToShow.push(pagination.currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
pagesToShow.sort(function(a, b) {
|
||||||
|
return parseInt(a, 10) - parseInt(b, 10);
|
||||||
|
});
|
||||||
|
return pagesToShow;
|
||||||
|
}
|
||||||
|
|
||||||
|
pagination.loadPage = function(page, callback) {
|
||||||
|
page = parseInt(page, 10);
|
||||||
|
if(!utils.isNumber(page) || page < 1 || page > pagination.pageCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ajaxify.go(window.location.pathname.slice(1) + '?page=' + page, function() {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
function updatePageLinks() {
|
||||||
|
$('.pagination').toggleClass('hide', pagination.pageCount === 0 || pagination.pageCount === 1);
|
||||||
|
|
||||||
|
$('.pagination .next').toggleClass('disabled', pagination.currentPage === pagination.pageCount);
|
||||||
|
$('.pagination .previous').toggleClass('disabled', pagination.currentPage === 1);
|
||||||
|
|
||||||
|
$('.pagination .page').removeClass('active');
|
||||||
|
$('.pagination .page[data-page="' + pagination.currentPage + '"]').addClass('active');
|
||||||
|
$('.pagination .page').each(function(index, element) {
|
||||||
|
var li = $(this);
|
||||||
|
var page = li.attr('data-page');
|
||||||
|
li.find('a').attr('href', window.location.pathname + '?page=' + page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pagination;
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, socket*/
|
||||||
|
|
||||||
|
define('forum/popular', ['forum/recent', 'forum/infinitescroll'], function(recent, infinitescroll) {
|
||||||
|
var Popular = {};
|
||||||
|
|
||||||
|
Popular.init = function() {
|
||||||
|
app.enterRoom('recent_posts');
|
||||||
|
|
||||||
|
$('#new-topics-alert').on('click', function() {
|
||||||
|
$(this).addClass('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
recent.selectActivePill();
|
||||||
|
};
|
||||||
|
|
||||||
|
return Popular;
|
||||||
|
});
|
@ -0,0 +1,140 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, socket, utils */
|
||||||
|
|
||||||
|
define('forum/recent', ['forum/infinitescroll'], function(infinitescroll) {
|
||||||
|
var Recent = {};
|
||||||
|
|
||||||
|
var newTopicCount = 0,
|
||||||
|
newPostCount = 0;
|
||||||
|
|
||||||
|
var active = '';
|
||||||
|
|
||||||
|
function getActiveSection() {
|
||||||
|
var url = window.location.href,
|
||||||
|
parts = url.split('/'),
|
||||||
|
active = parts[parts.length - 1];
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.start', function(ev, data) {
|
||||||
|
if(data.url.indexOf('recent') !== 0) {
|
||||||
|
Recent.removeListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Recent.init = function() {
|
||||||
|
app.enterRoom('recent_posts');
|
||||||
|
|
||||||
|
Recent.watchForNewPosts();
|
||||||
|
|
||||||
|
active = Recent.selectActivePill();
|
||||||
|
|
||||||
|
$('#new-topics-alert').on('click', function() {
|
||||||
|
$(this).addClass('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
infinitescroll.init(Recent.loadMoreTopics);
|
||||||
|
};
|
||||||
|
|
||||||
|
Recent.selectActivePill = function() {
|
||||||
|
var active = getActiveSection();
|
||||||
|
|
||||||
|
$('.nav-pills li').removeClass('active');
|
||||||
|
$('.nav-pills li a').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
if ($this.attr('href').match(active)) {
|
||||||
|
$this.parent().addClass('active');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return active;
|
||||||
|
};
|
||||||
|
|
||||||
|
Recent.watchForNewPosts = function () {
|
||||||
|
newPostCount = 0;
|
||||||
|
newTopicCount = 0;
|
||||||
|
Recent.removeListeners();
|
||||||
|
socket.on('event:new_topic', onNewTopic);
|
||||||
|
socket.on('event:new_post', onNewPost);
|
||||||
|
};
|
||||||
|
|
||||||
|
function onNewTopic(data) {
|
||||||
|
++newTopicCount;
|
||||||
|
Recent.updateAlertText();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNewPost(data) {
|
||||||
|
++newPostCount;
|
||||||
|
Recent.updateAlertText();
|
||||||
|
}
|
||||||
|
|
||||||
|
Recent.removeListeners = function() {
|
||||||
|
socket.removeListener('event:new_topic', onNewTopic);
|
||||||
|
socket.removeListener('event:new_post', onNewPost);
|
||||||
|
};
|
||||||
|
|
||||||
|
Recent.updateAlertText = function() {
|
||||||
|
var text = 'There';
|
||||||
|
|
||||||
|
if (newTopicCount > 1) {
|
||||||
|
text += ' are ' + newTopicCount + ' new topics';
|
||||||
|
} else if (newTopicCount === 1) {
|
||||||
|
text += ' is a new topic';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPostCount > 1) {
|
||||||
|
text += (newTopicCount?' and ':' are ') + newPostCount + ' new posts';
|
||||||
|
} else if(newPostCount === 1) {
|
||||||
|
text += (newTopicCount?' and ':' is ') + ' a new post';
|
||||||
|
}
|
||||||
|
|
||||||
|
text += '. Click here to reload.';
|
||||||
|
|
||||||
|
$('#new-topics-alert').html(text).removeClass('hide').fadeIn('slow');
|
||||||
|
$('#category-no-topics').addClass('hide');
|
||||||
|
};
|
||||||
|
|
||||||
|
Recent.loadMoreTopics = function(direction) {
|
||||||
|
if(direction < 0 || !$('#topics-container').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('topics.loadMoreRecentTopics', {
|
||||||
|
after: $('#topics-container').attr('data-nextstart'),
|
||||||
|
term: active
|
||||||
|
}, function(data, done) {
|
||||||
|
if (data.topics && data.topics.length) {
|
||||||
|
Recent.onTopicsLoaded('recent', data.topics, false, done);
|
||||||
|
$('#topics-container').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Recent.onTopicsLoaded = function(templateName, topics, showSelect, callback) {
|
||||||
|
|
||||||
|
topics = topics.filter(function(topic) {
|
||||||
|
return !$('#topics-container li[data-tid=' + topic.tid + ']').length;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!topics.length) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.parseAndTranslate(templateName, 'topics', {topics: topics, showSelect: showSelect}, function(html) {
|
||||||
|
$('#category-no-topics').remove();
|
||||||
|
|
||||||
|
$('#topics-container').append(html);
|
||||||
|
html.find('span.timeago').timeago();
|
||||||
|
app.createUserTooltips();
|
||||||
|
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Recent;
|
||||||
|
});
|
@ -0,0 +1,198 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, utils, socket, config */
|
||||||
|
|
||||||
|
|
||||||
|
define('forum/register', function() {
|
||||||
|
var Register = {},
|
||||||
|
validationError = false,
|
||||||
|
successIcon = '<i class="fa fa-check"></i>';
|
||||||
|
|
||||||
|
function showError(element, msg) {
|
||||||
|
translator.translate(msg, function(msg) {
|
||||||
|
element.html(msg);
|
||||||
|
element.parent()
|
||||||
|
.removeClass('alert-success')
|
||||||
|
.addClass('alert-danger');
|
||||||
|
element.show();
|
||||||
|
});
|
||||||
|
validationError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(element, msg) {
|
||||||
|
translator.translate(msg, function(msg) {
|
||||||
|
element.html(msg);
|
||||||
|
element.parent()
|
||||||
|
.removeClass('alert-danger')
|
||||||
|
.addClass('alert-success');
|
||||||
|
element.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEmail(email, callback) {
|
||||||
|
callback = callback || function() {};
|
||||||
|
var email_notify = $('#email-notify');
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
validationError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!utils.isEmailValid(email)) {
|
||||||
|
showError(email_notify, '[[error:invalid-email]]');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('user.emailExists', {
|
||||||
|
email: email
|
||||||
|
}, function(err, exists) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
showError(email_notify, '[[error:email-taken]]');
|
||||||
|
} else {
|
||||||
|
showSuccess(email_notify, successIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateUsername(username, callback) {
|
||||||
|
callback = callback || function() {};
|
||||||
|
|
||||||
|
var username_notify = $('#username-notify');
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
validationError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username.length < config.minimumUsernameLength) {
|
||||||
|
showError(username_notify, '[[error:username-too-short]]');
|
||||||
|
} else if (username.length > config.maximumUsernameLength) {
|
||||||
|
showError(username_notify, '[[error:username-too-long]]');
|
||||||
|
} else if (!utils.isUserNameValid(username) || !utils.slugify(username)) {
|
||||||
|
showError(username_notify, '[[error:invalid-username]]');
|
||||||
|
} else {
|
||||||
|
socket.emit('user.exists', {
|
||||||
|
username: username
|
||||||
|
}, function(err, exists) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
showError(username_notify, '[[error:username-taken]]');
|
||||||
|
} else {
|
||||||
|
showSuccess(username_notify, successIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePassword(password, password_confirm) {
|
||||||
|
if (!password) {
|
||||||
|
validationError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var password_notify = $('#password-notify'),
|
||||||
|
password_confirm_notify = $('#password-confirm-notify');
|
||||||
|
|
||||||
|
if (password.length < config.minimumPasswordLength) {
|
||||||
|
showError(password_notify, '[[user:change_password_error_length]]');
|
||||||
|
} else if (!utils.isPasswordValid(password)) {
|
||||||
|
showError(password_notify, '[[user:change_password_error]]');
|
||||||
|
} else {
|
||||||
|
showSuccess(password_notify, successIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== password_confirm && password_confirm !== '') {
|
||||||
|
showError(password_confirm_notify, '[[user:change_password_error_match]]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePasswordConfirm(password, password_confirm) {
|
||||||
|
var password_notify = $('#password-notify'),
|
||||||
|
password_confirm_notify = $('#password-confirm-notify');
|
||||||
|
|
||||||
|
if (!password || password_notify.hasClass('alert-error')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== password_confirm) {
|
||||||
|
showError(password_confirm_notify, '[[user:change_password_error_match]]');
|
||||||
|
} else {
|
||||||
|
showSuccess(password_confirm_notify, successIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Register.init = function() {
|
||||||
|
var email = $('#email'),
|
||||||
|
username = $('#username'),
|
||||||
|
password = $('#password'),
|
||||||
|
password_confirm = $('#password-confirm'),
|
||||||
|
register = $('#register'),
|
||||||
|
agreeTerms = $('#agree-terms');
|
||||||
|
|
||||||
|
$('#referrer').val(app.previousUrl);
|
||||||
|
|
||||||
|
email.on('blur', function() {
|
||||||
|
validateEmail(email.val());
|
||||||
|
});
|
||||||
|
|
||||||
|
username.on('keyup', function() {
|
||||||
|
$('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
|
||||||
|
});
|
||||||
|
|
||||||
|
username.on('blur', function() {
|
||||||
|
validateUsername(username.val());
|
||||||
|
});
|
||||||
|
|
||||||
|
password.on('blur', function() {
|
||||||
|
validatePassword(password.val(), password_confirm.val());
|
||||||
|
});
|
||||||
|
|
||||||
|
password_confirm.on('blur', function() {
|
||||||
|
validatePasswordConfirm(password.val(), password_confirm.val());
|
||||||
|
});
|
||||||
|
|
||||||
|
function validateForm(callback) {
|
||||||
|
validationError = false;
|
||||||
|
validatePassword(password.val(), password_confirm.val());
|
||||||
|
validatePasswordConfirm(password.val(), password_confirm.val());
|
||||||
|
|
||||||
|
validateEmail(email.val(), function() {
|
||||||
|
validateUsername(username.val(), callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
register.on('click', function(e) {
|
||||||
|
var registerBtn = $(this);
|
||||||
|
e.preventDefault();
|
||||||
|
validateForm(function() {
|
||||||
|
if (!validationError) {
|
||||||
|
registerBtn.parents('form').trigger('submit');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if(agreeTerms.length) {
|
||||||
|
agreeTerms.on('click', function() {
|
||||||
|
if ($(this).prop('checked')) {
|
||||||
|
register.removeAttr('disabled');
|
||||||
|
} else {
|
||||||
|
register.attr('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
register.attr('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Register;
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
define('forum/reset', function() {
|
||||||
|
var ResetPassword = {};
|
||||||
|
|
||||||
|
ResetPassword.init = function() {
|
||||||
|
var inputEl = $('#email'),
|
||||||
|
errorEl = $('#error'),
|
||||||
|
successEl = $('#success');
|
||||||
|
|
||||||
|
$('#reset').on('click', function() {
|
||||||
|
if (inputEl.val() && inputEl.val().indexOf('@') !== -1) {
|
||||||
|
socket.emit('user.reset.send', inputEl.val(), function(err, data) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorEl.addClass('hide').hide();
|
||||||
|
successEl.removeClass('hide').show();
|
||||||
|
inputEl.val('');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
successEl.addClass('hide').hide();
|
||||||
|
errorEl.removeClass('hide').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return ResetPassword;
|
||||||
|
});
|
@ -0,0 +1,55 @@
|
|||||||
|
define('forum/reset_code', function() {
|
||||||
|
var ResetCode = {};
|
||||||
|
|
||||||
|
ResetCode.init = function() {
|
||||||
|
var reset_code = ajaxify.variables.get('reset_code');
|
||||||
|
|
||||||
|
var resetEl = $('#reset'),
|
||||||
|
password = $('#password'),
|
||||||
|
repeat = $('#repeat'),
|
||||||
|
noticeEl = $('#notice');
|
||||||
|
|
||||||
|
resetEl.on('click', function() {
|
||||||
|
if (password.val().length < 6) {
|
||||||
|
$('#error').addClass('hide').hide();
|
||||||
|
noticeEl.find('strong').html('Invalid Password');
|
||||||
|
noticeEl.find('p').html('The password entered is too short, please pick a different password.');
|
||||||
|
noticeEl.removeClass('hide').css({display: 'block'});
|
||||||
|
} else if (password.value !== repeat.value) {
|
||||||
|
$('#error').hide();
|
||||||
|
noticeEl.find('strong').html('Invalid Password');
|
||||||
|
noticeEl.find('p').html('The two passwords you\'ve entered do not match.');
|
||||||
|
noticeEl.removeClass('hide').css({display: 'block'});
|
||||||
|
} else {
|
||||||
|
socket.emit('user.reset.commit', {
|
||||||
|
code: reset_code,
|
||||||
|
password: password.val()
|
||||||
|
}, function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
$('#error').addClass('hide').hide();
|
||||||
|
$('#notice').addClass('hide').hide();
|
||||||
|
$('#success').removeClass('hide').addClass('show').show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('user.reset.valid', reset_code, function(err, valid) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
resetEl.prop('disabled', false);
|
||||||
|
} else {
|
||||||
|
var formEl = $('#reset-form');
|
||||||
|
// Show error message
|
||||||
|
$('#error').show();
|
||||||
|
formEl.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return ResetCode;
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
define('forum/search', ['search'], function(searchModule) {
|
||||||
|
var Search = {};
|
||||||
|
|
||||||
|
Search.init = function() {
|
||||||
|
var searchQuery = $('#post-results').attr('data-search-query');
|
||||||
|
var regexes = [];
|
||||||
|
var searchTerms = searchQuery.split(' ');
|
||||||
|
for (var i=0; i<searchTerms.length; ++i) {
|
||||||
|
var regex = new RegExp(searchTerms[i], 'gi');
|
||||||
|
regexes.push({regex: regex, term: searchTerms[i]});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.search-result-text').each(function() {
|
||||||
|
var result = $(this);
|
||||||
|
var text = result.html();
|
||||||
|
for(var i=0; i<regexes.length; ++i) {
|
||||||
|
text = text.replace(regexes[i].regex, '<strong>' + regexes[i].term + '</strong>');
|
||||||
|
}
|
||||||
|
result.html(text).find('img').addClass('img-responsive');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#search-form input').val(searchQuery);
|
||||||
|
|
||||||
|
$('#mobile-search-form').off('submit').on('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var input = $(this).find('input');
|
||||||
|
|
||||||
|
searchModule.query(input.val(), function() {
|
||||||
|
input.val('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Search;
|
||||||
|
});
|
@ -0,0 +1,42 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, socket */
|
||||||
|
|
||||||
|
define('forum/tag', ['forum/recent', 'forum/infinitescroll'], function(recent, infinitescroll) {
|
||||||
|
var Tag = {};
|
||||||
|
|
||||||
|
Tag.init = function() {
|
||||||
|
app.enterRoom('tags');
|
||||||
|
|
||||||
|
if ($('body').height() <= $(window).height() && $('#topics-container').children().length >= 20) {
|
||||||
|
$('#load-more-btn').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#load-more-btn').on('click', function() {
|
||||||
|
loadMoreTopics();
|
||||||
|
});
|
||||||
|
|
||||||
|
infinitescroll.init(loadMoreTopics);
|
||||||
|
|
||||||
|
function loadMoreTopics(direction) {
|
||||||
|
if(direction < 0 || !$('#topics-container').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('topics.loadMoreFromSet', {
|
||||||
|
set: 'tag:' + ajaxify.variables.get('tag') + ':topics',
|
||||||
|
after: $('#topics-container').attr('data-nextstart')
|
||||||
|
}, function(data, done) {
|
||||||
|
if (data.topics && data.topics.length) {
|
||||||
|
recent.onTopicsLoaded('tag', data.topics, false, done);
|
||||||
|
$('#topics-container').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
$('#load-more-btn').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Tag;
|
||||||
|
});
|
@ -0,0 +1,59 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, utils, socket */
|
||||||
|
|
||||||
|
define('forum/tags', ['forum/infinitescroll'], function(infinitescroll) {
|
||||||
|
var Tags = {};
|
||||||
|
var timeoutId = 0;
|
||||||
|
|
||||||
|
Tags.init = function() {
|
||||||
|
app.enterRoom('tags');
|
||||||
|
|
||||||
|
$('#tag-search').on('input propertychange', function() {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = 0;
|
||||||
|
}
|
||||||
|
timeoutId = setTimeout(function() {
|
||||||
|
socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, results) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
onTagsLoaded(results, true, function() {
|
||||||
|
timeoutId = 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
infinitescroll.init(Tags.loadMoreTags);
|
||||||
|
};
|
||||||
|
|
||||||
|
Tags.loadMoreTags = function(direction) {
|
||||||
|
if(direction < 0 || !$('.tag-list').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('topics.loadMoreTags', {
|
||||||
|
after: $('.tag-list').attr('data-nextstart')
|
||||||
|
}, function(data, done) {
|
||||||
|
if (data && data.tags && data.tags.length) {
|
||||||
|
onTagsLoaded(data.tags, false, done);
|
||||||
|
$('.tag-list').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function onTagsLoaded(tags, replace, callback) {
|
||||||
|
callback = callback || function() {};
|
||||||
|
infinitescroll.parseAndTranslate('tags', 'tags', {tags: tags}, function(html) {
|
||||||
|
$('.tag-list')[replace ? 'html' : 'append'](html);
|
||||||
|
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Tags;
|
||||||
|
});
|
@ -0,0 +1,452 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
/* globals define, app, templates, translator, socket, bootbox, config, ajaxify, RELATIVE_PATH, utils */
|
||||||
|
|
||||||
|
var dependencies = [
|
||||||
|
'forum/pagination',
|
||||||
|
'forum/infinitescroll',
|
||||||
|
'forum/topic/threadTools',
|
||||||
|
'forum/topic/postTools',
|
||||||
|
'forum/topic/events',
|
||||||
|
'forum/topic/browsing',
|
||||||
|
'navigator'
|
||||||
|
];
|
||||||
|
|
||||||
|
define('forum/topic', dependencies, function(pagination, infinitescroll, threadTools, postTools, events, browsing, navigator) {
|
||||||
|
var Topic = {},
|
||||||
|
currentUrl = '';
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.start', function(ev, data) {
|
||||||
|
if(data.url.indexOf('topic') !== 0) {
|
||||||
|
navigator.hide();
|
||||||
|
$('.header-topic-title').find('span').text('').hide();
|
||||||
|
app.removeAlert('bookmark');
|
||||||
|
|
||||||
|
events.removeListeners();
|
||||||
|
|
||||||
|
socket.removeListener('event:new_post', onNewPost);
|
||||||
|
socket.removeListener('event:new_notification', onNewNotification);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Topic.init = function() {
|
||||||
|
var tid = ajaxify.variables.get('topic_id'),
|
||||||
|
thread_state = {
|
||||||
|
locked: ajaxify.variables.get('locked'),
|
||||||
|
deleted: ajaxify.variables.get('deleted'),
|
||||||
|
pinned: ajaxify.variables.get('pinned')
|
||||||
|
},
|
||||||
|
postCount = ajaxify.variables.get('postcount');
|
||||||
|
|
||||||
|
$(window).trigger('action:topic.loading');
|
||||||
|
|
||||||
|
app.enterRoom('topic_' + tid);
|
||||||
|
|
||||||
|
processPage($('.topic'));
|
||||||
|
|
||||||
|
showBottomPostBar();
|
||||||
|
|
||||||
|
postTools.init(tid, thread_state);
|
||||||
|
threadTools.init(tid, thread_state);
|
||||||
|
events.init();
|
||||||
|
|
||||||
|
handleSorting();
|
||||||
|
|
||||||
|
hidePostToolsForDeletedPosts();
|
||||||
|
|
||||||
|
enableInfiniteLoadingOrPagination();
|
||||||
|
|
||||||
|
addBlockQuoteHandler();
|
||||||
|
|
||||||
|
addBlockquoteEllipses($('.topic .post-content > blockquote'));
|
||||||
|
|
||||||
|
handleBookmark(tid);
|
||||||
|
|
||||||
|
navigator.init('.posts > .post-row', postCount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback, Topic.calculateIndex);
|
||||||
|
|
||||||
|
socket.on('event:new_post', onNewPost);
|
||||||
|
socket.on('event:new_notification', onNewNotification);
|
||||||
|
|
||||||
|
$(window).on('scroll', updateTopicTitle);
|
||||||
|
|
||||||
|
$(window).trigger('action:topic.loaded');
|
||||||
|
|
||||||
|
socket.emit('topics.enter', tid);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topic.toTop = function() {
|
||||||
|
navigator.scrollTop(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topic.toBottom = function() {
|
||||||
|
socket.emit('topics.postcount', ajaxify.variables.get('topic_id'), function(err, postCount) {
|
||||||
|
if (config.topicPostSort !== 'oldest_to_newest') {
|
||||||
|
postCount = 1;
|
||||||
|
}
|
||||||
|
navigator.scrollBottom(postCount - 1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleBookmark(tid) {
|
||||||
|
var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
|
||||||
|
var postIndex = getPostIndex();
|
||||||
|
if (postIndex) {
|
||||||
|
navigator.scrollToPost(postIndex - 1, true);
|
||||||
|
} else if (bookmark && (!config.usePagination || (config.usePagination && pagination.currentPage === 1)) && ajaxify.variables.get('postcount') > 1) {
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'bookmark',
|
||||||
|
message: '[[topic:bookmark_instructions]]',
|
||||||
|
timeout: 0,
|
||||||
|
type: 'info',
|
||||||
|
clickfn : function() {
|
||||||
|
navigator.scrollToPost(parseInt(bookmark, 10), true);
|
||||||
|
},
|
||||||
|
closefn : function() {
|
||||||
|
localStorage.removeItem('topic:' + tid + ':bookmark');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPostIndex() {
|
||||||
|
var parts = window.location.pathname.split('/');
|
||||||
|
return parts[4] ? parseInt(parts[4], 10) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSorting() {
|
||||||
|
var threadSort = $('.thread-sort');
|
||||||
|
threadSort.find('i').removeClass('fa-check');
|
||||||
|
var currentSetting = threadSort.find('a[data-sort="' + config.topicPostSort + '"]');
|
||||||
|
currentSetting.find('i').addClass('fa-check');
|
||||||
|
|
||||||
|
$('.thread-sort').on('click', 'a', function() {
|
||||||
|
var newSetting = $(this).attr('data-sort');
|
||||||
|
socket.emit('user.setTopicSort', newSetting, function(err) {
|
||||||
|
config.topicPostSort = newSetting;
|
||||||
|
ajaxify.go('topic/' + ajaxify.variables.get('topic_slug'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBottomPostBar() {
|
||||||
|
if($('#post-container .post-row').length > 1 || !$('#post-container li[data-index="0"]').length) {
|
||||||
|
$('.bottom-post-bar').removeClass('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNewPost(data) {
|
||||||
|
var tid = ajaxify.variables.get('topic_id');
|
||||||
|
if(data && data.posts && data.posts.length && data.posts[0].tid !== tid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.usePagination) {
|
||||||
|
return onNewPostPagination(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0; i<data.posts.length; ++i) {
|
||||||
|
var postcount = $('.user_postcount_' + data.posts[i].uid);
|
||||||
|
postcount.html(parseInt(postcount.html(), 10) + 1);
|
||||||
|
}
|
||||||
|
socket.emit('topics.markAsRead', [tid]);
|
||||||
|
createNewPosts(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNewNotification(data) {
|
||||||
|
var tid = ajaxify.variables.get('topic_id');
|
||||||
|
if (data && data.tid && parseInt(data.tid, 10) === parseInt(tid, 10)) {
|
||||||
|
socket.emit('topics.markTopicNotificationsRead', tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBlockQuoteHandler() {
|
||||||
|
$('#post-container').on('click', 'blockquote .toggle', function() {
|
||||||
|
var blockQuote = $(this).parent('blockquote');
|
||||||
|
var toggle = $(this);
|
||||||
|
blockQuote.toggleClass('uncollapsed');
|
||||||
|
var collapsed = !blockQuote.hasClass('uncollapsed');
|
||||||
|
toggle.toggleClass('fa-angle-down', collapsed).toggleClass('fa-angle-up', !collapsed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBlockquoteEllipses(blockquotes) {
|
||||||
|
blockquotes.each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
if ($this.find(':hidden:not(br)').length && !$this.find('.toggle').length) {
|
||||||
|
$this.append('<i class="fa fa-angle-down pointer toggle"></i>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableInfiniteLoadingOrPagination() {
|
||||||
|
if(!config.usePagination) {
|
||||||
|
infinitescroll.init(loadMorePosts, $('#post-container .post-row[data-index="0"]').height());
|
||||||
|
} else {
|
||||||
|
navigator.hide();
|
||||||
|
|
||||||
|
pagination.init(parseInt(ajaxify.variables.get('currentPage'), 10), parseInt(ajaxify.variables.get('pageCount'), 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePostToolsForDeletedPosts() {
|
||||||
|
$('#post-container li.deleted').each(function() {
|
||||||
|
postTools.toggle($(this).attr('data-pid'), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateTopicTitle() {
|
||||||
|
if($(window).scrollTop() > 50) {
|
||||||
|
$('.header-topic-title').find('span').text(ajaxify.variables.get('topic_name')).show();
|
||||||
|
} else {
|
||||||
|
$('.header-topic-title').find('span').text('').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Topic.calculateIndex = function(index, elementCount) {
|
||||||
|
if (index !== 1 && config.topicPostSort !== 'oldest_to_newest') {
|
||||||
|
return elementCount - index + 2;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
};
|
||||||
|
|
||||||
|
Topic.navigatorCallback = function(element, elementCount) {
|
||||||
|
var path = ajaxify.removeRelativePath(window.location.pathname.slice(1));
|
||||||
|
if (!path.startsWith('topic')) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
var postIndex = parseInt(element.attr('data-index'), 10);
|
||||||
|
var index = postIndex + 1;
|
||||||
|
if (config.topicPostSort !== 'oldest_to_newest') {
|
||||||
|
if (postIndex === 0) {
|
||||||
|
index = 1;
|
||||||
|
} else {
|
||||||
|
index = Math.max(elementCount - postIndex + 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentBookmark = localStorage.getItem('topic:' + ajaxify.variables.get('topic_id') + ':bookmark');
|
||||||
|
|
||||||
|
if (!currentBookmark || parseInt(postIndex, 10) >= parseInt(currentBookmark, 10)) {
|
||||||
|
localStorage.setItem('topic:' + ajaxify.variables.get('topic_id') + ':bookmark', postIndex);
|
||||||
|
app.removeAlert('bookmark');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!navigator.scrollActive) {
|
||||||
|
var parts = ajaxify.removeRelativePath(window.location.pathname.slice(1)).split('/');
|
||||||
|
var topicId = parts[1],
|
||||||
|
slug = parts[2];
|
||||||
|
var newUrl = 'topic/' + topicId + '/' + (slug ? slug : '');
|
||||||
|
if (postIndex > 0) {
|
||||||
|
newUrl += '/' + (postIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newUrl !== currentUrl) {
|
||||||
|
if (history.replaceState) {
|
||||||
|
var search = (window.location.search ? window.location.search : '');
|
||||||
|
history.replaceState({
|
||||||
|
url: newUrl + search
|
||||||
|
}, null, window.location.protocol + '//' + window.location.host + RELATIVE_PATH + '/' + newUrl + search);
|
||||||
|
}
|
||||||
|
currentUrl = newUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
};
|
||||||
|
|
||||||
|
function onNewPostPagination(data) {
|
||||||
|
var posts = data.posts;
|
||||||
|
socket.emit('topics.getPageCount', ajaxify.variables.get('topic_id'), function(err, newPageCount) {
|
||||||
|
|
||||||
|
pagination.recreatePaginationLinks(newPageCount);
|
||||||
|
|
||||||
|
if (pagination.currentPage === pagination.pageCount) {
|
||||||
|
createNewPosts(data);
|
||||||
|
} else if(data.posts && data.posts.length && parseInt(data.posts[0].uid, 10) === parseInt(app.uid, 10)) {
|
||||||
|
pagination.loadPage(pagination.pageCount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewPosts(data, callback) {
|
||||||
|
callback = callback || function() {};
|
||||||
|
if(!data || (data.posts && !data.posts.length)) {
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAlreadyAddedPosts() {
|
||||||
|
data.posts = data.posts.filter(function(post) {
|
||||||
|
return $('#post-container li[data-pid="' + post.pid +'"]').length === 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var after = null,
|
||||||
|
before = null;
|
||||||
|
|
||||||
|
function findInsertionPoint() {
|
||||||
|
var firstPostTimestamp = parseInt(data.posts[0].timestamp, 10);
|
||||||
|
var firstPostVotes = parseInt(data.posts[0].votes, 10);
|
||||||
|
var firstPostPid = data.posts[0].pid;
|
||||||
|
|
||||||
|
var firstReply = $('#post-container li.post-row[data-index!="0"]').first();
|
||||||
|
var lastReply = $('#post-container li.post-row[data-index!="0"]').last();
|
||||||
|
|
||||||
|
if (config.topicPostSort === 'oldest_to_newest') {
|
||||||
|
if (firstPostTimestamp < parseInt(firstReply.attr('data-timestamp'), 10)) {
|
||||||
|
before = firstReply;
|
||||||
|
} else if(firstPostTimestamp >= parseInt(lastReply.attr('data-timestamp'), 10)) {
|
||||||
|
after = lastReply;
|
||||||
|
}
|
||||||
|
} else if(config.topicPostSort === 'newest_to_oldest') {
|
||||||
|
if (firstPostTimestamp > parseInt(firstReply.attr('data-timestamp'), 10)) {
|
||||||
|
before = firstReply;
|
||||||
|
} else if(firstPostTimestamp <= parseInt(lastReply.attr('data-timestamp'), 10)) {
|
||||||
|
after = lastReply;
|
||||||
|
}
|
||||||
|
} else if(config.topicPostSort === 'most_votes') {
|
||||||
|
if (firstPostVotes > parseInt(firstReply.attr('data-votes'), 10)) {
|
||||||
|
before = firstReply;
|
||||||
|
} else if(firstPostVotes < parseInt(firstReply.attr('data-votes'), 10)) {
|
||||||
|
after = lastReply;
|
||||||
|
} else {
|
||||||
|
if (firstPostPid > firstReply.attr('data-pid')) {
|
||||||
|
before = firstReply;
|
||||||
|
} else if(firstPostPid <= firstReply.attr('data-pid')) {
|
||||||
|
after = lastReply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAlreadyAddedPosts();
|
||||||
|
if(!data.posts.length) {
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
findInsertionPoint();
|
||||||
|
|
||||||
|
data.title = $('<div></div>').text(ajaxify.variables.get('topic_name')).html();
|
||||||
|
data.viewcount = ajaxify.variables.get('viewcount');
|
||||||
|
|
||||||
|
infinitescroll.parseAndTranslate('topic', 'posts', data, function(html) {
|
||||||
|
if(after) {
|
||||||
|
html.insertAfter(after);
|
||||||
|
} else if(before) {
|
||||||
|
// Save document height and position for future reference (about 5 lines down)
|
||||||
|
var height = $(document).height(),
|
||||||
|
scrollTop = $(document).scrollTop(),
|
||||||
|
originalPostEl = $('li[data-index="0"]');
|
||||||
|
|
||||||
|
// Insert the new post
|
||||||
|
html.insertBefore(before);
|
||||||
|
|
||||||
|
// If the user is not at the top of the page... (or reasonably so...)
|
||||||
|
if (scrollTop > originalPostEl.offset().top) {
|
||||||
|
// Now restore the relative position the user was on prior to new post insertion
|
||||||
|
$(document).scrollTop(scrollTop + ($(document).height() - height));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$('#post-container').append(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.hide().fadeIn('slow');
|
||||||
|
|
||||||
|
addBlockquoteEllipses(html.find('.post-content > blockquote'));
|
||||||
|
|
||||||
|
$(window).trigger('action:posts.loaded');
|
||||||
|
onNewPostsLoaded(html, data.posts);
|
||||||
|
callback(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNewPostsLoaded(html, posts) {
|
||||||
|
|
||||||
|
var pids = [];
|
||||||
|
for(var i=0; i<posts.length; ++i) {
|
||||||
|
pids.push(posts[i].pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('posts.getPrivileges', pids, function(err, privileges) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i=0; i<pids.length; ++i) {
|
||||||
|
toggleModTools(pids[i], privileges[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
processPage(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processPage(element) {
|
||||||
|
app.createUserTooltips();
|
||||||
|
app.replaceSelfLinks(element.find('a'));
|
||||||
|
utils.addCommasToNumbers(element.find('.formatted-number'));
|
||||||
|
utils.makeNumbersHumanReadable(element.find('.human-readable-number'));
|
||||||
|
element.find('span.timeago').timeago();
|
||||||
|
element.find('.post-content img:not(.emoji)').addClass('img-responsive').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
if (!$this.parent().is('a')) {
|
||||||
|
$this.wrap('<a href="' + $this.attr('src') + '" target="_blank">');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
postTools.updatePostCount();
|
||||||
|
showBottomPostBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleModTools(pid, privileges) {
|
||||||
|
var postEl = $('.post-row[data-pid="' + pid + '"]');
|
||||||
|
|
||||||
|
postEl.find('.edit, .delete').toggleClass('hidden', !privileges.editable);
|
||||||
|
postEl.find('.move').toggleClass('hidden', !privileges.move);
|
||||||
|
postEl.find('.reply, .quote').toggleClass('hidden', !$('.post_reply').length);
|
||||||
|
var isSelfPost = parseInt(postEl.attr('data-uid'), 10) === parseInt(app.uid, 10);
|
||||||
|
postEl.find('.chat, .flag').toggleClass('hidden', isSelfPost || !app.uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMorePosts(direction) {
|
||||||
|
if (!$('#post-container').length || navigator.scrollActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reverse = config.topicPostSort === 'newest_to_oldest' || config.topicPostSort === 'most_votes';
|
||||||
|
|
||||||
|
infinitescroll.calculateAfter(direction, '#post-container .post-row[data-index!="0"]', config.postsPerPage, reverse, function(after, offset, el) {
|
||||||
|
loadPostsAfter(after);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPostsAfter(after) {
|
||||||
|
var tid = ajaxify.variables.get('topic_id');
|
||||||
|
if (!utils.isNumber(tid) || !utils.isNumber(after) || (after === 0 && $('#post-container li.post-row[data-index="1"]').length)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var indicatorEl = $('.loading-indicator');
|
||||||
|
if (!indicatorEl.is(':animated')) {
|
||||||
|
indicatorEl.fadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('topics.loadMore', {
|
||||||
|
tid: tid,
|
||||||
|
after: after
|
||||||
|
}, function (data, done) {
|
||||||
|
|
||||||
|
indicatorEl.fadeOut();
|
||||||
|
|
||||||
|
if (data && data.posts && data.posts.length) {
|
||||||
|
createNewPosts(data, function(postsCreated) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
hidePostToolsForDeletedPosts();
|
||||||
|
} else {
|
||||||
|
navigator.update();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Topic;
|
||||||
|
});
|
@ -0,0 +1,95 @@
|
|||||||
|
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, translator, config, socket, ajaxify */
|
||||||
|
|
||||||
|
define('forum/topic/browsing', function() {
|
||||||
|
|
||||||
|
var Browsing = {};
|
||||||
|
|
||||||
|
Browsing.onUpdateUsersInRoom = function(data) {
|
||||||
|
if(data && data.room.indexOf('topic_' + ajaxify.variables.get('topic_id')) !== -1) {
|
||||||
|
for(var i=0; i<data.users.length; ++i) {
|
||||||
|
addUserIcon(data.users[i]);
|
||||||
|
}
|
||||||
|
getReplyingUsers();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Browsing.onUserEnter = function(data) {
|
||||||
|
var activeEl = $('.thread_active_users');
|
||||||
|
var user = activeEl.find('a[data-uid="' + data.uid + '"]');
|
||||||
|
if (!user.length && activeEl.children().length < 10) {
|
||||||
|
addUserIcon(data);
|
||||||
|
} else {
|
||||||
|
user.attr('data-count', parseInt(user.attr('data-count'), 10) + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Browsing.onUserLeave = function(uid) {
|
||||||
|
var activeEl = $('.thread_active_users');
|
||||||
|
var user = activeEl.find('a[data-uid="' + uid + '"]');
|
||||||
|
if (user.length) {
|
||||||
|
var count = Math.max(0, parseInt(user.attr('data-count'), 10) - 1);
|
||||||
|
user.attr('data-count', count);
|
||||||
|
if (count <= 0) {
|
||||||
|
user.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Browsing.onUserStatusChange = function(data) {
|
||||||
|
updateOnlineIcon($('.username-field[data-uid="' + data.uid + '"]'), data.status);
|
||||||
|
|
||||||
|
updateBrowsingUsers(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateOnlineIcon(el, status) {
|
||||||
|
translator.translate('[[global:' + status + ']]', function(translated) {
|
||||||
|
el.siblings('i')
|
||||||
|
.attr('class', 'fa fa-circle status ' + status)
|
||||||
|
.attr('title', translated)
|
||||||
|
.attr('data-original-title', translated);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBrowsingUsers(data) {
|
||||||
|
var activeEl = $('.thread_active_users');
|
||||||
|
var user = activeEl.find('a[data-uid="'+ data.uid + '"]');
|
||||||
|
if (user.length && data.status === 'offline') {
|
||||||
|
user.parent().remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserIcon(user) {
|
||||||
|
if (!user.userslug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var activeEl = $('.thread_active_users');
|
||||||
|
var userEl = createUserIcon(user.uid, user.picture, user.userslug, user.username);
|
||||||
|
activeEl.append(userEl);
|
||||||
|
activeEl.find('a[data-uid] img').tooltip({
|
||||||
|
placement: 'top'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUserIcon(uid, picture, userslug, username) {
|
||||||
|
if(!$('.thread_active_users').find('[data-uid="' + uid + '"]').length) {
|
||||||
|
return $('<div class="inline-block"><a data-uid="' + uid + '" data-count="1" href="' + config.relative_path + '/user/' + userslug + '"><img title="' + username + '" src="'+ picture +'"/></a></div>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReplyingUsers() {
|
||||||
|
var activeEl = $('.thread_active_users');
|
||||||
|
socket.emit('modules.composer.getUsersByTid', ajaxify.variables.get('topic_id'), function(err, uids) {
|
||||||
|
if (uids && uids.length) {
|
||||||
|
for(var x=0;x<uids.length;x++) {
|
||||||
|
activeEl.find('[data-uid="' + uids[x] + '"]').addClass('replying');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Browsing;
|
||||||
|
});
|
@ -0,0 +1,180 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals app, ajaxify, define, socket, translator, templates */
|
||||||
|
|
||||||
|
define('forum/topic/events', ['forum/topic/browsing', 'forum/topic/postTools', 'forum/topic/threadTools'], function(browsing, postTools, threadTools) {
|
||||||
|
|
||||||
|
var Events = {};
|
||||||
|
|
||||||
|
var events = {
|
||||||
|
'event:update_users_in_room': browsing.onUpdateUsersInRoom,
|
||||||
|
'event:user_enter': browsing.onUserEnter,
|
||||||
|
'event:user_leave': browsing.onUserLeave,
|
||||||
|
'event:user_status_change': browsing.onUserStatusChange,
|
||||||
|
'event:voted': updatePostVotesAndUserReputation,
|
||||||
|
'event:favourited': updateFavouriteCount,
|
||||||
|
|
||||||
|
'event:topic_deleted': toggleTopicDeleteState,
|
||||||
|
'event:topic_restored': toggleTopicDeleteState,
|
||||||
|
'event:topic_purged': onTopicPurged,
|
||||||
|
|
||||||
|
'event:topic_locked': threadTools.setLockedState,
|
||||||
|
'event:topic_unlocked': threadTools.setLockedState,
|
||||||
|
|
||||||
|
'event:topic_pinned': threadTools.setPinnedState,
|
||||||
|
'event:topic_unpinned': threadTools.setPinnedState,
|
||||||
|
|
||||||
|
'event:topic_moved': onTopicMoved,
|
||||||
|
|
||||||
|
'event:post_edited': onPostEdited,
|
||||||
|
'event:post_purged': onPostPurged,
|
||||||
|
|
||||||
|
'event:post_deleted': togglePostDeleteState,
|
||||||
|
'event:post_restored': togglePostDeleteState,
|
||||||
|
|
||||||
|
'posts.favourite': togglePostFavourite,
|
||||||
|
'posts.unfavourite': togglePostFavourite,
|
||||||
|
|
||||||
|
'posts.upvote': togglePostVote,
|
||||||
|
'posts.downvote': togglePostVote,
|
||||||
|
'posts.unvote': togglePostVote,
|
||||||
|
|
||||||
|
'event:topic.toggleReply': toggleReply,
|
||||||
|
};
|
||||||
|
|
||||||
|
Events.init = function() {
|
||||||
|
Events.removeListeners();
|
||||||
|
for(var eventName in events) {
|
||||||
|
if (events.hasOwnProperty(eventName)) {
|
||||||
|
socket.on(eventName, events[eventName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Events.removeListeners = function() {
|
||||||
|
for(var eventName in events) {
|
||||||
|
if (events.hasOwnProperty(eventName)) {
|
||||||
|
socket.removeListener(eventName, events[eventName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function updatePostVotesAndUserReputation(data) {
|
||||||
|
var votes = $('li[data-pid="' + data.post.pid + '"] .votes'),
|
||||||
|
reputationElements = $('.reputation[data-uid="' + data.post.uid + '"]');
|
||||||
|
|
||||||
|
votes.html(data.post.votes).attr('data-votes', data.post.votes);
|
||||||
|
reputationElements.html(data.user.reputation).attr('data-reputation', data.user.reputation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFavouriteCount(data) {
|
||||||
|
$('li[data-pid="' + data.post.pid + '"] .favouriteCount').html(data.post.reputation).attr('data-favourites', data.post.reputation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTopicDeleteState(data) {
|
||||||
|
threadTools.setLockedState(data);
|
||||||
|
threadTools.setDeleteState(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopicPurged(tid) {
|
||||||
|
ajaxify.go('category/' + ajaxify.variables.get('category_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopicMoved(data) {
|
||||||
|
if (data && data.tid > 0) {
|
||||||
|
ajaxify.go('topic/' + data.tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostEdited(data) {
|
||||||
|
var editedPostEl = $('#content_' + data.pid),
|
||||||
|
editedPostTitle = $('#topic_title_' + data.pid);
|
||||||
|
|
||||||
|
if (editedPostTitle.length) {
|
||||||
|
editedPostTitle.fadeOut(250, function() {
|
||||||
|
editedPostTitle.html(data.title).fadeIn(250);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editedPostEl.fadeOut(250, function() {
|
||||||
|
editedPostEl.html(data.content);
|
||||||
|
editedPostEl.find('img').addClass('img-responsive');
|
||||||
|
app.replaceSelfLinks(editedPostEl.find('a'));
|
||||||
|
editedPostEl.fadeIn(250);
|
||||||
|
|
||||||
|
$(window).trigger('action:posts.edited');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.tags && data.tags.length !== $('.tags').first().children().length) {
|
||||||
|
ajaxify.loadTemplate('partials/post_bar', function(postBarTemplate) {
|
||||||
|
var html = templates.parse(templates.getBlock(postBarTemplate, 'tags'), {
|
||||||
|
tags: data.tags
|
||||||
|
});
|
||||||
|
var tags = $('.tags');
|
||||||
|
tags.fadeOut(250, function() {
|
||||||
|
tags.html(html).fadeIn(250);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostPurged(pid) {
|
||||||
|
$('#post-container li[data-pid="' + pid + '"]').fadeOut(500, function() {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePostDeleteState(data) {
|
||||||
|
var postEl = $('#post-container li[data-pid="' + data.pid + '"]');
|
||||||
|
|
||||||
|
if (!postEl.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postEl.toggleClass('deleted');
|
||||||
|
var isDeleted = postEl.hasClass('deleted');
|
||||||
|
postTools.toggle(data.pid, isDeleted);
|
||||||
|
|
||||||
|
if (!app.isAdmin && parseInt(data.uid, 10) !== parseInt(app.uid, 10)) {
|
||||||
|
if (isDeleted) {
|
||||||
|
postEl.find('.post-content').translateHtml('[[topic:post_is_deleted]]');
|
||||||
|
} else {
|
||||||
|
postEl.find('.post-content').html(data.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postTools.updatePostCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePostFavourite(data) {
|
||||||
|
var favBtn = $('li[data-pid="' + data.post.pid + '"] .favourite');
|
||||||
|
if (!favBtn.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
favBtn.addClass('btn-warning')
|
||||||
|
.attr('data-favourited', data.isFavourited);
|
||||||
|
|
||||||
|
var icon = favBtn.find('i');
|
||||||
|
var className = icon.attr('class');
|
||||||
|
|
||||||
|
if (data.isFavourited ? className.indexOf('-o') !== -1 : className.indexOf('-o') === -1) {
|
||||||
|
icon.attr('class', data.isFavourited ? className.replace('-o', '') : className + '-o');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePostVote(data) {
|
||||||
|
var post = $('li[data-pid="' + data.post.pid + '"]');
|
||||||
|
|
||||||
|
post.find('.upvote').toggleClass('btn-primary upvoted', data.upvote);
|
||||||
|
post.find('.downvote').toggleClass('btn-primary downvoted', data.downvote);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleReply(data) {
|
||||||
|
$('.thread_active_users [data-uid="' + data.uid + '"]').toggleClass('replying', data.isReplying);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Events;
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,136 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, translator, socket */
|
||||||
|
|
||||||
|
define('forum/topic/fork', function() {
|
||||||
|
|
||||||
|
var Fork = {},
|
||||||
|
forkModal,
|
||||||
|
forkCommit,
|
||||||
|
pids = [];
|
||||||
|
|
||||||
|
Fork.init = function() {
|
||||||
|
$('.fork_thread').on('click', onForkThreadClicked);
|
||||||
|
};
|
||||||
|
|
||||||
|
function disableClicks() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableClicksOnPosts() {
|
||||||
|
$('.post-row').on('click', 'button,a', disableClicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableClicksOnPosts() {
|
||||||
|
$('.post-row').off('click', 'button,a', disableClicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onForkThreadClicked() {
|
||||||
|
forkModal = $('#fork-thread-modal');
|
||||||
|
forkCommit = forkModal.find('#fork_thread_commit');
|
||||||
|
pids.length = 0;
|
||||||
|
|
||||||
|
showForkModal();
|
||||||
|
showNoPostsSelected();
|
||||||
|
|
||||||
|
forkModal.find('.close,#fork_thread_cancel').on('click', closeForkModal);
|
||||||
|
forkModal.find('#fork-title').on('change', checkForkButtonEnable);
|
||||||
|
$('#post-container').on('click', 'li[data-pid]', function() {
|
||||||
|
togglePostSelection($(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
disableClicksOnPosts();
|
||||||
|
|
||||||
|
forkCommit.on('click', createTopicFromPosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showForkModal() {
|
||||||
|
forkModal.removeClass('hide')
|
||||||
|
.css('position', 'fixed')
|
||||||
|
.css('left', Math.max(0, (($(window).width() - $(forkModal).outerWidth()) / 2) + $(window).scrollLeft()) + 'px')
|
||||||
|
.css('top', '0px')
|
||||||
|
.css('z-index', '2000');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTopicFromPosts() {
|
||||||
|
socket.emit('topics.createTopicFromPosts', {
|
||||||
|
title: forkModal.find('#fork-title').val(),
|
||||||
|
pids: pids
|
||||||
|
}, function(err, newTopic) {
|
||||||
|
function fadeOutAndRemove(pid) {
|
||||||
|
$('#post-container li[data-pid="' + pid + '"]').fadeOut(500, function() {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alert({
|
||||||
|
timeout: 5000,
|
||||||
|
title: '[[global:alert.success]]',
|
||||||
|
message: '[[topic:fork_success]]',
|
||||||
|
type: 'success',
|
||||||
|
clickfn: function() {
|
||||||
|
ajaxify.go('topic/' + newTopic.slug);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for(var i=0; i<pids.length; ++i) {
|
||||||
|
fadeOutAndRemove(pids[i]);
|
||||||
|
}
|
||||||
|
closeForkModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePostSelection(post) {
|
||||||
|
var newPid = post.attr('data-pid');
|
||||||
|
|
||||||
|
if(parseInt(post.attr('data-index'), 10) === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newPid) {
|
||||||
|
var index = pids.indexOf(newPid);
|
||||||
|
if(index === -1) {
|
||||||
|
pids.push(newPid);
|
||||||
|
post.css('opacity', '0.5');
|
||||||
|
} else {
|
||||||
|
pids.splice(index, 1);
|
||||||
|
post.css('opacity', '1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pids.length) {
|
||||||
|
pids.sort();
|
||||||
|
forkModal.find('#fork-pids').html(pids.toString());
|
||||||
|
} else {
|
||||||
|
showNoPostsSelected();
|
||||||
|
}
|
||||||
|
checkForkButtonEnable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNoPostsSelected() {
|
||||||
|
forkModal.find('#fork-pids').translateHtml('[[topic:fork_no_pids]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForkButtonEnable() {
|
||||||
|
if(forkModal.find('#fork-title').length && pids.length) {
|
||||||
|
forkCommit.removeAttr('disabled');
|
||||||
|
} else {
|
||||||
|
forkCommit.attr('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeForkModal() {
|
||||||
|
for(var i=0; i<pids.length; ++i) {
|
||||||
|
$('#post-container li[data-pid="' + pids[i] + '"]').css('opacity', 1);
|
||||||
|
}
|
||||||
|
forkModal.addClass('hide');
|
||||||
|
$('#post-container').off('click', 'li[data-pid]');
|
||||||
|
enableClicksOnPosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Fork;
|
||||||
|
});
|
@ -0,0 +1,100 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, socket */
|
||||||
|
|
||||||
|
define('forum/topic/move', function() {
|
||||||
|
|
||||||
|
var Move = {},
|
||||||
|
modal,
|
||||||
|
targetCid,
|
||||||
|
targetCategoryLabel;
|
||||||
|
|
||||||
|
Move.init = function(tids, currentCid, onComplete) {
|
||||||
|
modal = $('#move_thread_modal');
|
||||||
|
|
||||||
|
Move.tids = tids;
|
||||||
|
Move.currentCid = currentCid;
|
||||||
|
Move.onComplete = onComplete;
|
||||||
|
Move.moveAll = tids ? false : true;
|
||||||
|
|
||||||
|
modal.on('shown.bs.modal', onMoveModalShown);
|
||||||
|
$('#move-confirm').hide();
|
||||||
|
|
||||||
|
if (Move.moveAll || (tids && tids.length > 1)) {
|
||||||
|
modal.find('.modal-header h3').translateText('[[topic:move_topics]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.modal('show');
|
||||||
|
};
|
||||||
|
|
||||||
|
function onMoveModalShown() {
|
||||||
|
var loadingEl = $('#categories-loading');
|
||||||
|
if (!loadingEl.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('categories.get', onCategoriesLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCategoriesLoaded(err, categories) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCategories(categories);
|
||||||
|
|
||||||
|
modal.on('click', '.category-list li[data-cid]', function(e) {
|
||||||
|
selectCategory($(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#move_thread_commit').on('click', onCommitClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCategory(category) {
|
||||||
|
modal.find('#confirm-category-name').html(category.html());
|
||||||
|
$('#move-confirm').show();
|
||||||
|
|
||||||
|
targetCid = category.attr('data-cid');
|
||||||
|
targetCategoryLabel = category.html();
|
||||||
|
$('#move_thread_commit').prop('disabled', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCommitClicked() {
|
||||||
|
var commitEl = $('#move_thread_commit');
|
||||||
|
|
||||||
|
if (!commitEl.prop('disabled') && targetCid) {
|
||||||
|
commitEl.prop('disabled', true);
|
||||||
|
|
||||||
|
moveTopics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveTopics() {
|
||||||
|
socket.emit(Move.moveAll ? 'topics.moveAll' : 'topics.move', {
|
||||||
|
tids: Move.tids,
|
||||||
|
cid: targetCid,
|
||||||
|
currentCid: Move.currentCid
|
||||||
|
}, function(err) {
|
||||||
|
modal.modal('hide');
|
||||||
|
$('#move_thread_commit').prop('disabled', false);
|
||||||
|
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('[[topic:topic_move_success, ' + targetCategoryLabel + ']]');
|
||||||
|
if (typeof Move.onComplete === 'function') {
|
||||||
|
Move.onComplete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCategories(categories) {
|
||||||
|
templates.parse('partials/category_list', {categories: categories}, function(html) {
|
||||||
|
modal.find('.modal-body').prepend(html);
|
||||||
|
$('#categories-loading').remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Move;
|
||||||
|
});
|
@ -0,0 +1,322 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, translator, ajaxify, socket, bootbox */
|
||||||
|
|
||||||
|
define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(composer, share, navigator) {
|
||||||
|
|
||||||
|
var PostTools = {},
|
||||||
|
topicName;
|
||||||
|
|
||||||
|
PostTools.init = function(tid, threadState) {
|
||||||
|
topicName = ajaxify.variables.get('topic_name');
|
||||||
|
|
||||||
|
addPostHandlers(tid, threadState);
|
||||||
|
|
||||||
|
share.addShareHandlers(topicName);
|
||||||
|
|
||||||
|
addVoteHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
PostTools.toggle = function(pid, isDeleted) {
|
||||||
|
var postEl = $('#post-container li[data-pid="' + pid + '"]');
|
||||||
|
|
||||||
|
postEl.find('.quote, .favourite, .post_reply, .chat').toggleClass('hidden', isDeleted);
|
||||||
|
postEl.find('.purge').toggleClass('hidden', !isDeleted);
|
||||||
|
postEl.find('.delete .i').toggleClass('fa-trash-o', !isDeleted).toggleClass('fa-history', isDeleted);
|
||||||
|
postEl.find('.delete span').translateHtml(isDeleted ? ' [[topic:restore]]' : ' [[topic:delete]]');
|
||||||
|
};
|
||||||
|
|
||||||
|
PostTools.updatePostCount = function() {
|
||||||
|
socket.emit('topics.postcount', ajaxify.variables.get('topic_id'), function(err, postCount) {
|
||||||
|
if (!err) {
|
||||||
|
$('.topic-post-count').html(postCount);
|
||||||
|
navigator.setCount(postCount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function addVoteHandler() {
|
||||||
|
$('#post-container').on('mouseenter', '.post-row .votes', function() {
|
||||||
|
loadDataAndCreateTooltip($(this), 'posts.getUpvoters');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDataAndCreateTooltip(el, method) {
|
||||||
|
var pid = el.parents('.post-row').attr('data-pid');
|
||||||
|
socket.emit(method, pid, function(err, data) {
|
||||||
|
if (!err) {
|
||||||
|
createTooltip(el, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTooltip(el, data) {
|
||||||
|
var usernames = data.usernames;
|
||||||
|
if (!usernames.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (usernames.length + data.otherCount > 6) {
|
||||||
|
usernames = usernames.join(', ').replace(/,/g, '|');
|
||||||
|
translator.translate('[[topic:users_and_others, ' + usernames + ', ' + data.otherCount + ']]', function(translated) {
|
||||||
|
translated = translated.replace(/\|/g, ',');
|
||||||
|
el.attr('title', translated).tooltip('destroy').tooltip('show');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
usernames = usernames.join(', ');
|
||||||
|
el.attr('title', usernames).tooltip('destroy').tooltip('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPostHandlers(tid, threadState) {
|
||||||
|
$('.topic').on('click', '.post_reply', function() {
|
||||||
|
if (!threadState.locked) {
|
||||||
|
onReplyClicked($(this), tid, topicName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var postContainer = $('#post-container');
|
||||||
|
|
||||||
|
postContainer.on('click', '.quote', function() {
|
||||||
|
if (!threadState.locked) {
|
||||||
|
onQuoteClicked($(this), tid, topicName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.favourite', function() {
|
||||||
|
favouritePost($(this), getData($(this), 'data-pid'));
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.upvote', function() {
|
||||||
|
return toggleVote($(this), '.upvoted', 'posts.upvote');
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.downvote', function() {
|
||||||
|
return toggleVote($(this), '.downvoted', 'posts.downvote');
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.flag', function() {
|
||||||
|
flagPost(getData($(this), 'data-pid'));
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.edit', function(e) {
|
||||||
|
composer.editPost(getData($(this), 'data-pid'));
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.delete', function(e) {
|
||||||
|
deletePost($(this), tid);
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.purge', function(e) {
|
||||||
|
purgePost($(this), tid);
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.move', function(e) {
|
||||||
|
openMovePostModal($(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
postContainer.on('click', '.chat', function(e) {
|
||||||
|
openChat($(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReplyClicked(button, tid, topicName) {
|
||||||
|
var selectionText = '',
|
||||||
|
selection = window.getSelection ? window.getSelection() : document.selection.createRange();
|
||||||
|
|
||||||
|
if ($(selection.baseNode).parents('.post-content').length > 0) {
|
||||||
|
var snippet = selection.toString();
|
||||||
|
if (snippet.length) {
|
||||||
|
selectionText = '> ' + snippet.replace(/\n/g, '\n> ') + '\n\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var username = getUserName(selectionText ? $(selection.baseNode) : button);
|
||||||
|
if (getData(button, 'data-uid') === '0') {
|
||||||
|
username = '';
|
||||||
|
}
|
||||||
|
if (selectionText.length) {
|
||||||
|
composer.addQuote(tid, ajaxify.variables.get('topic_slug'), getData(button, 'data-index'), getData(button, 'data-pid'), topicName, username, selectionText);
|
||||||
|
} else {
|
||||||
|
composer.newReply(tid, getData(button, 'data-pid'), topicName, username ? username + ' ' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onQuoteClicked(button, tid, topicName) {
|
||||||
|
var username = getUserName(button),
|
||||||
|
pid = getData(button, 'data-pid');
|
||||||
|
|
||||||
|
socket.emit('posts.getRawPost', pid, function(err, post) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
var quoted = '';
|
||||||
|
if(post) {
|
||||||
|
quoted = '> ' + post.replace(/\n/g, '\n> ') + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($('.composer').length) {
|
||||||
|
composer.addQuote(tid, ajaxify.variables.get('topic_slug'), getData(button, 'data-index'), pid, topicName, username, quoted);
|
||||||
|
} else {
|
||||||
|
composer.newReply(tid, pid, topicName, '[[modules:composer.user_said, ' + username + ']]\n' + quoted);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function favouritePost(button, pid) {
|
||||||
|
var method = button.attr('data-favourited') === 'false' ? 'posts.favourite' : 'posts.unfavourite';
|
||||||
|
|
||||||
|
socket.emit(method, {
|
||||||
|
pid: pid,
|
||||||
|
room_id: app.currentRoom
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
app.alertError(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleVote(button, className, method) {
|
||||||
|
var post = button.parents('.post-row'),
|
||||||
|
currentState = post.find(className).length;
|
||||||
|
|
||||||
|
socket.emit(currentState ? 'posts.unvote' : method , {
|
||||||
|
pid: post.attr('data-pid'),
|
||||||
|
room_id: app.currentRoom
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
app.alertError(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(button, data) {
|
||||||
|
return button.parents('.post-row').attr(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserName(button) {
|
||||||
|
var username = '',
|
||||||
|
post = button.parents('li[data-pid]');
|
||||||
|
|
||||||
|
if (post.length) {
|
||||||
|
username = post.attr('data-username').replace(/\s/g, '-');
|
||||||
|
}
|
||||||
|
if (post.length && post.attr('data-uid') !== '0') {
|
||||||
|
username = '@' + username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletePost(button, tid) {
|
||||||
|
var pid = getData(button, 'data-pid'),
|
||||||
|
postEl = $('#post-container li[data-pid="' + pid + '"]'),
|
||||||
|
action = !postEl.hasClass('deleted') ? 'delete' : 'restore';
|
||||||
|
|
||||||
|
postAction(action, pid, tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function purgePost(button, tid) {
|
||||||
|
postAction('purge', getData(button, 'data-pid'), tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function postAction(action, pid, tid) {
|
||||||
|
translator.translate('[[topic:post_' + action + '_confirm]]', function(msg) {
|
||||||
|
bootbox.confirm(msg, function(confirm) {
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('posts.' + action, {
|
||||||
|
pid: pid,
|
||||||
|
tid: tid
|
||||||
|
}, function(err) {
|
||||||
|
if(err) {
|
||||||
|
app.alertError(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMovePostModal(button) {
|
||||||
|
var moveModal = $('#move-post-modal'),
|
||||||
|
moveBtn = moveModal.find('#move_post_commit'),
|
||||||
|
topicId = moveModal.find('#topicId');
|
||||||
|
|
||||||
|
showMoveModal();
|
||||||
|
|
||||||
|
moveModal.find('.close,#move_post_cancel').on('click', function() {
|
||||||
|
moveModal.addClass('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
topicId.on('change', function() {
|
||||||
|
if(topicId.val().length) {
|
||||||
|
moveBtn.removeAttr('disabled');
|
||||||
|
} else {
|
||||||
|
moveBtn.attr('disabled', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
moveBtn.on('click', function() {
|
||||||
|
movePost(button.parents('.post-row'), getData(button, 'data-pid'), topicId.val());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMoveModal() {
|
||||||
|
$('#move-post-modal').removeClass('hide')
|
||||||
|
.css("position", "fixed")
|
||||||
|
.css("left", Math.max(0, (($(window).width() - $($('#move-post-modal')).outerWidth()) / 2) + $(window).scrollLeft()) + "px")
|
||||||
|
.css("top", "0px")
|
||||||
|
.css("z-index", "2000");
|
||||||
|
}
|
||||||
|
|
||||||
|
function movePost(post, pid, tid) {
|
||||||
|
socket.emit('topics.movePost', {pid: pid, tid: tid}, function(err) {
|
||||||
|
$('#move-post-modal').addClass('hide');
|
||||||
|
|
||||||
|
if(err) {
|
||||||
|
$('#topicId').val('');
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
post.fadeOut(500, function() {
|
||||||
|
post.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#topicId').val('');
|
||||||
|
|
||||||
|
app.alertSuccess('[[topic:post_moved]]');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function flagPost(pid) {
|
||||||
|
translator.translate('[[topic:flag_confirm]]', function(message) {
|
||||||
|
bootbox.confirm(message, function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('posts.flag', pid, function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('[[topic:flag_success]]');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openChat(button) {
|
||||||
|
var post = button.parents('li.post-row');
|
||||||
|
|
||||||
|
app.openChat(post.attr('data-username'), post.attr('data-uid'));
|
||||||
|
button.parents('.btn-group').find('.dropdown-toggle').click();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PostTools;
|
||||||
|
});
|
@ -0,0 +1,169 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, translator, ajaxify, socket, bootbox */
|
||||||
|
|
||||||
|
define('forum/topic/threadTools', ['forum/topic/fork', 'forum/topic/move'], function(fork, move) {
|
||||||
|
|
||||||
|
var ThreadTools = {};
|
||||||
|
|
||||||
|
ThreadTools.init = function(tid, threadState) {
|
||||||
|
ThreadTools.threadState = threadState;
|
||||||
|
|
||||||
|
if (threadState.locked) {
|
||||||
|
ThreadTools.setLockedState({tid: tid, isLocked: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadState.deleted) {
|
||||||
|
ThreadTools.setDeleteState({tid: tid, isDelete: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadState.pinned) {
|
||||||
|
ThreadTools.setPinnedState({tid: tid, isPinned: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.delete_thread').on('click', function() {
|
||||||
|
topicCommand(threadState.deleted ? 'restore' : 'delete', tid);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.purge_thread').on('click', function() {
|
||||||
|
topicCommand('purge', tid);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.lock_thread').on('click', function() {
|
||||||
|
socket.emit(threadState.locked ? 'topics.unlock' : 'topics.lock', {tids: [tid], cid: ajaxify.variables.get('category_id')});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.pin_thread').on('click', function() {
|
||||||
|
socket.emit(threadState.pinned ? 'topics.unpin' : 'topics.pin', {tids: [tid], cid: ajaxify.variables.get('category_id')});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.markAsUnreadForAll').on('click', function() {
|
||||||
|
var btn = $(this);
|
||||||
|
socket.emit('topics.markAsUnreadForAll', [tid], function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
|
||||||
|
btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click');
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.move_thread').on('click', function(e) {
|
||||||
|
move.init([tid], ajaxify.variables.get('category_id'));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
fork.init();
|
||||||
|
|
||||||
|
$('.posts').on('click', '.follow', function() {
|
||||||
|
socket.emit('topics.follow', tid, function(err, state) {
|
||||||
|
if(err) {
|
||||||
|
return app.alert({
|
||||||
|
type: 'danger',
|
||||||
|
alert_id: 'topic_follow',
|
||||||
|
title: '[[global:please_log_in]]',
|
||||||
|
message: '[[topic:login_to_subscribe]]',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setFollowState(state);
|
||||||
|
|
||||||
|
app.alert({
|
||||||
|
alert_id: 'follow_thread',
|
||||||
|
message: state ? '[[topic:following_topic.message]]' : '[[topic:not_following_topic.message]]',
|
||||||
|
type: 'success',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function topicCommand(command, tid) {
|
||||||
|
translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) {
|
||||||
|
bootbox.confirm(msg, function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
socket.emit('topics.' + command, {tids: [tid], cid: ajaxify.variables.get('category_id')});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadTools.setLockedState = function(data) {
|
||||||
|
var threadEl = $('#post-container');
|
||||||
|
if (parseInt(data.tid, 10) === parseInt(threadEl.attr('data-tid'), 10)) {
|
||||||
|
var isLocked = data.isLocked && !app.isAdmin;
|
||||||
|
|
||||||
|
$('.lock_thread').translateHtml('<i class="fa fa-fw fa-' + (data.isLocked ? 'un': '') + 'lock"></i> [[topic:thread_tools.' + (data.isLocked ? 'un': '') + 'lock]]');
|
||||||
|
|
||||||
|
translator.translate(isLocked ? '[[topic:locked]]' : '[[topic:reply]]', function(translated) {
|
||||||
|
var className = isLocked ? 'fa-lock' : 'fa-reply';
|
||||||
|
threadEl.find('.post_reply').html('<i class="fa ' + className + '"></i> ' + translated);
|
||||||
|
$('.topic-main-buttons .post_reply').attr('disabled', isLocked).html(isLocked ? '<i class="fa fa-lock"></i> ' + translated : translated);
|
||||||
|
});
|
||||||
|
|
||||||
|
threadEl.find('.quote, .edit, .delete').toggleClass('hidden', isLocked);
|
||||||
|
$('.topic-title i.fa-lock').toggleClass('hide', !data.isLocked);
|
||||||
|
ThreadTools.threadState.locked = data.isLocked;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ThreadTools.setDeleteState = function(data) {
|
||||||
|
var threadEl = $('#post-container');
|
||||||
|
if (parseInt(data.tid, 10) !== parseInt(threadEl.attr('data-tid'), 10)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.delete_thread span').translateHtml('<i class="fa fa-fw ' + (data.isDelete ? 'fa-history' : 'fa-trash-o') + '"></i> [[topic:thread_tools.' + (data.isDelete ? 'restore' : 'delete') + ']]');
|
||||||
|
|
||||||
|
threadEl.toggleClass('deleted', data.isDelete);
|
||||||
|
ThreadTools.threadState.deleted = data.isDelete;
|
||||||
|
$('.purge_thread').toggleClass('hidden', !data.isDelete);
|
||||||
|
|
||||||
|
if (data.isDelete) {
|
||||||
|
translator.translate('[[topic:deleted_message]]', function(translated) {
|
||||||
|
$('<div id="thread-deleted" class="alert alert-warning">' + translated + '</div>').insertBefore(threadEl);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$('#thread-deleted').remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ThreadTools.setPinnedState = function(data) {
|
||||||
|
var threadEl = $('#post-container');
|
||||||
|
if (parseInt(data.tid, 10) === parseInt(threadEl.attr('data-tid'), 10)) {
|
||||||
|
translator.translate('<i class="fa fa-fw fa-thumb-tack"></i> [[topic:thread_tools.' + (data.isPinned ? 'unpin' : 'pin') + ']]', function(translated) {
|
||||||
|
$('.pin_thread').html(translated);
|
||||||
|
ThreadTools.threadState.pinned = data.isPinned;
|
||||||
|
});
|
||||||
|
$('.topic-title i.fa-thumb-tack').toggleClass('hide', !data.isPinned);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function setFollowState(state) {
|
||||||
|
var title = state ? '[[topic:unwatch.title]]' : '[[topic:watch.title]]';
|
||||||
|
var iconClass = state ? 'fa fa-eye-slash' : 'fa fa-eye';
|
||||||
|
var text = state ? '[[topic:unwatch]]' : '[[topic:watch]]';
|
||||||
|
|
||||||
|
var followEl = $('.posts .follow');
|
||||||
|
|
||||||
|
translator.translate(title, function(titleTranslated) {
|
||||||
|
followEl.attr('title', titleTranslated).find('i').attr('class', iconClass);
|
||||||
|
followEl.find('span').text(text);
|
||||||
|
|
||||||
|
translator.translate(followEl.html(), function(translated) {
|
||||||
|
followEl.html(translated);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return ThreadTools;
|
||||||
|
});
|
@ -0,0 +1,151 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, app, socket */
|
||||||
|
|
||||||
|
define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'], function(recent, topicSelect, infinitescroll) {
|
||||||
|
var Unread = {};
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.start', function(ev, data) {
|
||||||
|
if(data.url.indexOf('unread') !== 0) {
|
||||||
|
recent.removeListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Unread.init = function() {
|
||||||
|
app.enterRoom('recent_posts');
|
||||||
|
|
||||||
|
$('#new-topics-alert').on('click', function() {
|
||||||
|
$(this).addClass('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
recent.watchForNewPosts();
|
||||||
|
|
||||||
|
$('#markSelectedRead').on('click', function() {
|
||||||
|
var tids = topicSelect.getSelectedTids();
|
||||||
|
if(!tids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.emit('topics.markAsRead', tids, function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
doneRemovingTids(tids);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#markAllRead').on('click', function() {
|
||||||
|
socket.emit('topics.markAllRead', function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.alertSuccess('[[unread:topics_marked_as_read.success]]');
|
||||||
|
|
||||||
|
$('#topics-container').empty();
|
||||||
|
$('#category-no-topics').removeClass('hidden');
|
||||||
|
$('.markread').addClass('hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.markread').on('click', '.category', function() {
|
||||||
|
function getCategoryTids(cid) {
|
||||||
|
var tids = [];
|
||||||
|
$('#topics-container .category-item[data-cid="' + cid + '"]').each(function() {
|
||||||
|
tids.push($(this).attr('data-tid'));
|
||||||
|
});
|
||||||
|
return tids;
|
||||||
|
}
|
||||||
|
var cid = $(this).attr('data-cid');
|
||||||
|
var tids = getCategoryTids(cid);
|
||||||
|
|
||||||
|
socket.emit('topics.markCategoryTopicsRead', cid, function(err) {
|
||||||
|
if(err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
doneRemovingTids(tids);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('categories.get', onCategoriesLoaded);
|
||||||
|
|
||||||
|
topicSelect.init();
|
||||||
|
|
||||||
|
if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20) {
|
||||||
|
$('#load-more-btn').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#load-more-btn').on('click', function() {
|
||||||
|
loadMoreTopics();
|
||||||
|
});
|
||||||
|
|
||||||
|
infinitescroll.init(loadMoreTopics);
|
||||||
|
|
||||||
|
function loadMoreTopics(direction) {
|
||||||
|
if(direction < 0 || !$('#topics-container').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('topics.loadMoreUnreadTopics', {
|
||||||
|
after: $('#topics-container').attr('data-nextstart')
|
||||||
|
}, function(data, done) {
|
||||||
|
if (data.topics && data.topics.length) {
|
||||||
|
recent.onTopicsLoaded('unread', data.topics, true, done);
|
||||||
|
$('#topics-container').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
$('#load-more-btn').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function doneRemovingTids(tids) {
|
||||||
|
removeTids(tids);
|
||||||
|
|
||||||
|
app.alertSuccess('[[unread:topics_marked_as_read.success]]');
|
||||||
|
|
||||||
|
if (!$('#topics-container').children().length) {
|
||||||
|
$('#category-no-topics').removeClass('hidden');
|
||||||
|
$('.markread').addClass('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTids(tids) {
|
||||||
|
for(var i=0; i<tids.length; ++i) {
|
||||||
|
$('#topics-container .category-item[data-tid="' + tids[i] + '"]').remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCategoriesLoaded(err, categories) {
|
||||||
|
createCategoryLinks(categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCategoryLinks(categories) {
|
||||||
|
categories = categories.filter(function(category) {
|
||||||
|
return !category.disabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
for(var i=0; i<categories.length; ++i) {
|
||||||
|
createCategoryLink(categories[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCategoryLink(category) {
|
||||||
|
|
||||||
|
var link = $('<a role="menuitem" href="#"></a>');
|
||||||
|
if (category.icon) {
|
||||||
|
link.append('<i class="fa fa-fw ' + category.icon + '"></i> ' + category.name);
|
||||||
|
} else {
|
||||||
|
link.append(category.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$('<li role="presentation" class="category" data-cid="' + category.cid + '"></li>')
|
||||||
|
.append(link)
|
||||||
|
.appendTo($('.markread .dropdown-menu'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Unread;
|
||||||
|
});
|
@ -0,0 +1,186 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* globals define, socket, app, ajaxify, templates, translator*/
|
||||||
|
|
||||||
|
define('forum/users', function() {
|
||||||
|
var Users = {};
|
||||||
|
|
||||||
|
var loadingMoreUsers = false;
|
||||||
|
|
||||||
|
Users.init = function() {
|
||||||
|
|
||||||
|
var active = getActiveSection();
|
||||||
|
|
||||||
|
$('.nav-pills li').removeClass('active');
|
||||||
|
$('.nav-pills li a').each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
if ($this.attr('href').match(active)) {
|
||||||
|
$this.parent().addClass('active');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
handleSearch();
|
||||||
|
|
||||||
|
socket.removeListener('event:user_status_change', onUserStatusChange);
|
||||||
|
socket.on('event:user_status_change', onUserStatusChange);
|
||||||
|
|
||||||
|
$('#load-more-users-btn').on('click', loadMoreUsers);
|
||||||
|
|
||||||
|
$(window).off('scroll').on('scroll', function() {
|
||||||
|
var bottom = ($(document).height() - $(window).height()) * 0.9;
|
||||||
|
|
||||||
|
if ($(window).scrollTop() > bottom && !loadingMoreUsers) {
|
||||||
|
loadMoreUsers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadMoreUsers() {
|
||||||
|
var set = '';
|
||||||
|
var activeSection = getActiveSection();
|
||||||
|
if (activeSection === 'latest') {
|
||||||
|
set = 'users:joindate';
|
||||||
|
} else if (activeSection === 'sort-posts') {
|
||||||
|
set = 'users:postcount';
|
||||||
|
} else if (activeSection === 'sort-reputation') {
|
||||||
|
set = 'users:reputation';
|
||||||
|
} else if (activeSection === 'online' || activeSection === 'users') {
|
||||||
|
set = 'users:online';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (set) {
|
||||||
|
startLoading(set, $('#users-container').children('.registered-user').length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLoading(set, after) {
|
||||||
|
loadingMoreUsers = true;
|
||||||
|
|
||||||
|
socket.emit('user.loadMore', {
|
||||||
|
set: set,
|
||||||
|
after: after
|
||||||
|
}, function(err, data) {
|
||||||
|
if (data && data.users.length) {
|
||||||
|
onUsersLoaded(data.users);
|
||||||
|
$('#load-more-users-btn').removeClass('disabled');
|
||||||
|
} else {
|
||||||
|
$('#load-more-users-btn').addClass('disabled');
|
||||||
|
}
|
||||||
|
loadingMoreUsers = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUsersLoaded(users) {
|
||||||
|
users = users.filter(function(user) {
|
||||||
|
return !$('.users-box[data-uid="' + user.uid + '"]').length;
|
||||||
|
});
|
||||||
|
|
||||||
|
ajaxify.loadTemplate('users', function(usersTemplate) {
|
||||||
|
var html = templates.parse(templates.getBlock(usersTemplate, 'users'), {users: users});
|
||||||
|
|
||||||
|
translator.translate(html, function(translated) {
|
||||||
|
$('#users-container').append(translated);
|
||||||
|
$('#users-container .anon-user').appendTo($('#users-container'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch() {
|
||||||
|
var timeoutId = 0;
|
||||||
|
var lastSearch = null;
|
||||||
|
|
||||||
|
$('#search-user').on('keyup', function() {
|
||||||
|
if (timeoutId !== 0) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(function() {
|
||||||
|
function reset() {
|
||||||
|
notify.html('<i class="fa fa-search"></i>');
|
||||||
|
notify.parent().removeClass('btn-warning label-warning btn-success label-success');
|
||||||
|
}
|
||||||
|
var username = $('#search-user').val();
|
||||||
|
var notify = $('#user-notfound-notify');
|
||||||
|
|
||||||
|
if (username === '') {
|
||||||
|
notify.html('<i class="fa fa-circle-o"></i>');
|
||||||
|
notify.parent().removeClass('btn-warning label-warning btn-success label-success');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSearch === username) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastSearch = username;
|
||||||
|
|
||||||
|
notify.html('<i class="fa fa-spinner fa-spin"></i>');
|
||||||
|
|
||||||
|
socket.emit('user.search', username, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
reset();
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ajaxify.loadTemplate('users', function(usersTemplate) {
|
||||||
|
var html = templates.parse(templates.getBlock(usersTemplate, 'users'), data);
|
||||||
|
|
||||||
|
translator.translate(html, function(translated) {
|
||||||
|
$('#users-container').html(translated);
|
||||||
|
if (!data.users.length) {
|
||||||
|
translator.translate('[[error:no-user]]', function(translated) {
|
||||||
|
notify.html(translated);
|
||||||
|
notify.parent().addClass('btn-warning label-warning');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
translator.translate('[[users:users-found-search-took, ' + data.users.length + ', ' + data.timing + ']]', function(translated) {
|
||||||
|
notify.html(translated);
|
||||||
|
notify.parent().addClass('btn-success label-success');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUserStatusChange(data) {
|
||||||
|
var section = getActiveSection();
|
||||||
|
if((section.indexOf('online') === 0 || section.indexOf('users') === 0)) {
|
||||||
|
updateUser(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser(data) {
|
||||||
|
if (data.status === 'offline') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var usersContainer = $('#users-container');
|
||||||
|
var userEl = usersContainer.find('li[data-uid="' + data.uid +'"]');
|
||||||
|
|
||||||
|
if (userEl.length) {
|
||||||
|
var statusEl = userEl.find('.status');
|
||||||
|
translator.translate('[[global:' + data.status + ']]', function(translated) {
|
||||||
|
statusEl.attr('class', 'fa fa-circle status ' + data.status)
|
||||||
|
.attr('title', translated)
|
||||||
|
.attr('data-original-title', translated);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveSection() {
|
||||||
|
var url = window.location.href,
|
||||||
|
parts = url.split('/');
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Users;
|
||||||
|
});
|
Loading…
Reference in New Issue