Merge remote-tracking branch 'origin/master' into user-icons

v1.18.x
Julian Lam 10 years ago
commit 526afc0910

@ -48,9 +48,9 @@
"nodebb-plugin-soundpack-default": "0.1.4",
"nodebb-plugin-spam-be-gone": "0.4.2",
"nodebb-rewards-essentials": "0.0.5",
"nodebb-theme-lavender": "2.0.10",
"nodebb-theme-persona": "3.0.62",
"nodebb-theme-vanilla": "4.0.29",
"nodebb-theme-lavender": "2.0.11",
"nodebb-theme-persona": "3.0.68",
"nodebb-theme-vanilla": "4.0.32",
"nodebb-widget-essentials": "2.0.3",
"npm": "^2.1.4",
"passport": "^0.3.0",
@ -67,7 +67,7 @@
"socket.io-redis": "^0.1.3",
"socketio-wildcard": "~0.1.1",
"string": "^3.0.0",
"templates.js": "0.3.0",
"templates.js": "0.2.16",
"uglify-js": "^2.4.24",
"underscore": "~1.8.3",
"underscore.deep": "^0.5.1",

@ -19,8 +19,6 @@
"request.notification_title": "Group Membership Request from <strong>%1</strong>",
"request.notification_text": "<strong>%1</strong> has requested to become a member of <strong>%2</strong>",
"cover-instructions": "Drag and Drop a photo, drag to position, and hit <strong>Save</strong>",
"cover-change": "Change",
"cover-save": "Save",
"cover-saving": "Saving",

@ -1,9 +1,7 @@
#navigation {
#active-navigation {
width: 100%;
.active {
background-color: #eee;
}
@ -17,6 +15,13 @@
.drag-item {
cursor: move;
margin-right: 10px;
padding: 8px 10px;
margin-bottom: 5px;
}
p {
line-height: 20px;
min-height: 40px;
}
}
@ -33,5 +38,4 @@
list-style-type: none;
padding: 0;
}
}
}

@ -2,7 +2,14 @@
/* globals define, ajaxify, app, utils, socket, bootbox */
define('forum/account/profile', ['forum/account/header', 'forum/infinitescroll', 'translator'], function(header, infinitescroll, translator) {
define('forum/account/profile', [
'forum/account/header',
'forum/infinitescroll',
'translator',
'coverPhoto',
'uploader',
'components'
], function(header, infinitescroll, translator, coverPhoto, uploader, components) {
var Account = {},
yourid,
theirid,
@ -41,6 +48,10 @@ define('forum/account/profile', ['forum/account/header', 'forum/infinitescroll',
socket.on('event:user_status_change', onUserStatusChange);
infinitescroll.init(loadMorePosts);
if (parseInt(yourid, 10) === parseInt(theirid, 10)) {
setupCoverPhoto();
}
};
function processPage() {
@ -161,5 +172,22 @@ define('forum/account/profile', ['forum/account/header', 'forum/infinitescroll',
});
}
function setupCoverPhoto() {
coverPhoto.init(components.get('account/cover'),
function(imageData, position, callback) {
socket.emit('user.updateCover', {
uid: yourid,
imageData: imageData,
position: position
}, callback);
},
function() {
uploader.open(RELATIVE_PATH + '/api/user/uploadcover', { uid: yourid }, 0, function(imageUrlOnServer) {
components.get('account/cover').css('background-image', 'url(' + imageUrlOnServer + ')');
});
}
);
}
return Account;
});

@ -1,18 +1,16 @@
"use strict";
/* globals define, socket, ajaxify, app, bootbox, utils */
/* globals define, socket, ajaxify, app, bootbox, utils, RELATIVE_PATH */
define('forum/groups/details', [
'forum/groups/memberlist',
'iconSelect',
'components',
'vendor/colorpicker/colorpicker',
'vendor/jquery/draggable-background/backgroundDraggable'
], function(memberList, iconSelect, components) {
var Details = {
cover: {}
};
'coverPhoto',
'uploader',
'vendor/colorpicker/colorpicker'
], function(memberList, iconSelect, components, coverPhoto, uploader) {
var Details = {};
var groupName;
Details.init = function() {
@ -22,7 +20,21 @@ define('forum/groups/details', [
if (ajaxify.data.group.isOwner) {
Details.prepareSettings();
Details.initialiseCover();
coverPhoto.init(components.get('groups/cover'),
function(imageData, position, callback) {
socket.emit('groups.cover.update', {
groupName: groupName,
imageData: imageData,
position: position
}, callback);
},
function() {
uploader.open(RELATIVE_PATH + '/api/groups/uploadpicture', { groupName: groupName }, 0, function(imageUrlOnServer) {
components.get('groups/cover').css('background-image', 'url(' + imageUrlOnServer + ')');
});
}
);
}
memberList.init();
@ -209,93 +221,6 @@ define('forum/groups/details', [
});
};
// Cover Photo Handling Code
Details.initialiseCover = function() {
var coverEl = components.get('groups/cover');
coverEl.find('.change').on('click', function() {
coverEl.toggleClass('active', 1);
coverEl.backgroundDraggable({
axis: 'y',
units: 'percent'
});
coverEl.on('dragover', Details.cover.onDragOver);
coverEl.on('drop', Details.cover.onDrop);
});
coverEl.find('.save').on('click', Details.cover.save);
coverEl.addClass('initialised');
};
Details.cover.load = function() {
socket.emit('groups.cover.get', {
groupName: groupName
}, function(err, data) {
if (!err) {
var coverEl = components.get('groups/cover');
if (data['cover:url']) {
coverEl.css('background-image', 'url(' + data['cover:url'] + ')');
}
if (data['cover:position']) {
coverEl.css('background-position', data['cover:position']);
}
delete Details.cover.newCover;
} else {
app.alertError(err.message);
}
});
};
Details.cover.onDragOver = function(e) {
e.stopPropagation();
e.preventDefault();
e.originalEvent.dataTransfer.dropEffect = 'copy';
};
Details.cover.onDrop = function(e) {
var coverEl = components.get('groups/cover');
e.stopPropagation();
e.preventDefault();
var files = e.originalEvent.dataTransfer.files,
reader = new FileReader();
if (files.length && files[0].type.match('image.*')) {
reader.onload = function(e) {
coverEl.css('background-image', 'url(' + e.target.result + ')');
Details.cover.newCover = e.target.result;
};
reader.readAsDataURL(files[0]);
}
};
Details.cover.save = function() {
var coverEl = components.get('groups/cover');
coverEl.addClass('saving');
socket.emit('groups.cover.update', {
groupName: groupName,
imageData: Details.cover.newCover || undefined,
position: components.get('groups/cover').css('background-position')
}, function(err) {
if (!err) {
coverEl.toggleClass('active', 0);
coverEl.backgroundDraggable('disable');
coverEl.off('dragover', Details.cover.onDragOver);
coverEl.off('drop', Details.cover.onDrop);
Details.cover.load();
} else {
app.alertError(err.message);
}
coverEl.removeClass('saving');
});
};
function handleMemberInvitations() {
if (ajaxify.data.group.isOwner) {
var searchInput = $('[component="groups/members/invite"]');

@ -18,6 +18,11 @@ define('forum/topic', [
currentUrl = '';
$(window).on('action:ajaxify.start', function(ev, data) {
if (Topic.replaceURLTimeout) {
clearTimeout(Topic.replaceURLTimeout);
Topic.replaceURLTimeout = 0;
}
if (ajaxify.currentPage !== data.url) {
navigator.disable();
components.get('navbar/title').find('span').text('').hide();

@ -118,6 +118,7 @@ define('forum/topic/events', [
editedPostEl.html(data.post.content);
editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive');
app.replaceSelfLinks(editedPostEl.find('a'));
posts.wrapImagesInLinks(editedPostEl.parent());
editedPostEl.fadeIn(250);
$(window).trigger('action:posts.edited', data);
});

@ -217,15 +217,19 @@ define('forum/topic/posts', [
utils.addCommasToNumbers(posts.find('.formatted-number'));
utils.makeNumbersHumanReadable(posts.find('.human-readable-number'));
posts.find('.timeago').timeago();
Posts.wrapImagesInLinks(posts);
addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote > blockquote'));
hidePostToolsForDeletedPosts(posts);
};
Posts.wrapImagesInLinks = function(posts) {
posts.find('[component="post/content"] img:not(.emoji)').each(function() {
var $this = $(this);
if (!$this.parent().is('a')) {
$this.wrap('<a href="' + $this.attr('src') + '" target="_blank">');
}
});
addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote > blockquote'));
hidePostToolsForDeletedPosts(posts);
};
Posts.showBottomPostBar = function() {

@ -0,0 +1,83 @@
"use strict";
/* globals define, app */
define('coverPhoto', [
'vendor/jquery/draggable-background/backgroundDraggable'
], function() {
var coverPhoto = {
coverEl: null,
saveFn: null
};
coverPhoto.init = function(coverEl, saveFn, uploadFn) {
coverPhoto.coverEl = coverEl;
coverPhoto.saveFn = saveFn;
coverEl.find('.upload').on('click', uploadFn);
coverEl.find('.resize').on('click', function() {
coverEl
.toggleClass('active', 1)
.backgroundDraggable({
axis: 'y',
units: 'percent'
});
});
coverEl
.on('dragover', coverPhoto.onDragOver)
.on('drop', coverPhoto.onDrop);
coverEl.find('.save').on('click', coverPhoto.save);
coverEl.addClass('initialised');
};
coverPhoto.onDragOver = function(e) {
e.stopPropagation();
e.preventDefault();
e.originalEvent.dataTransfer.dropEffect = 'copy';
};
coverPhoto.onDrop = function(e) {
e.stopPropagation();
e.preventDefault();
var files = e.originalEvent.dataTransfer.files,
reader = new FileReader();
if (files.length && files[0].type.match('image.*')) {
reader.onload = function(e) {
coverPhoto.coverEl.css('background-image', 'url(' + e.target.result + ')');
coverPhoto.newCover = e.target.result;
};
reader.readAsDataURL(files[0]);
coverPhoto.coverEl
.addClass('active', 1)
.backgroundDraggable({
axis: 'y',
units: 'percent'
});
}
};
coverPhoto.save = function() {
coverPhoto.coverEl.addClass('saving');
coverPhoto.saveFn(coverPhoto.newCover || undefined, coverPhoto.coverEl.css('background-position'), function(err) {
if (!err) {
coverPhoto.coverEl.toggleClass('active', 0);
coverPhoto.coverEl.backgroundDraggable('disable');
coverPhoto.coverEl.off('dragover', coverPhoto.onDragOver);
coverPhoto.coverEl.off('drop', coverPhoto.onDrop);
} else {
app.alertError(err.message);
}
coverPhoto.coverEl.removeClass('saving');
});
};
return coverPhoto;
});

@ -120,4 +120,19 @@ editController.uploadPicture = function (req, res, next) {
});
};
editController.uploadCoverPicture = function(req, res, next) {
var params = JSON.parse(req.body.params);
user.updateCoverPicture({
file: req.files.files[0].path,
uid: params.uid
}, function(err, image) {
if (err) {
return next(err);
}
res.json([{url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
});
};
module.exports = editController;

@ -3,6 +3,7 @@
var async = require('async'),
validator = require('validator'),
nconf = require('nconf'),
user = require('../../user'),
groups = require('../../groups'),
@ -118,7 +119,7 @@ helpers.getBaseUser = function(userslug, callerUID, callback) {
async.parallel({
user: function(next) {
user.getUserFields(uid, ['uid', 'username', 'userslug'], next);
user.getUserFields(uid, ['uid', 'username', 'userslug', 'picture', 'cover:url', 'cover:position'], next);
},
isAdmin: function(next) {
user.isAdministrator(callerUID, next);
@ -138,6 +139,10 @@ helpers.getBaseUser = function(userslug, callerUID, callback) {
results.user.isSelf = parseInt(callerUID, 10) === parseInt(results.user.uid, 10);
results.user.showHidden = results.user.isSelf || results.isAdmin;
results.user.profile_links = results.profile_links;
results['cover:url'] = results['cover:url'] || nconf.get('relative_path') + '/images/cover-default.png';
results['cover:position'] = results['cover:position'] || '50% 50%';
next(null, results.user);
}
], callback);

@ -112,13 +112,11 @@ apiController.getConfig = function(req, res, next) {
apiController.renderWidgets = function(req, res, next) {
var async = require('async'),
areas = {
template: req.query.template,
locations: req.query.locations,
url: req.query.url
},
renderedWidgets = [];
var areas = {
template: req.query.template,
locations: req.query.locations,
url: req.query.url
};
if (!areas.template || !areas.locations) {
return res.status(200).json({});

@ -3,7 +3,6 @@
var async = require('async'),
nconf = require('nconf'),
validator = require('validator'),
db = require('../database'),
meta = require('../meta'),
groups = require('../groups'),
user = require('../user'),
@ -132,4 +131,19 @@ groupsController.members = function(req, res, next) {
});
};
groupsController.uploadCover = function(req, res, next) {
var params = JSON.parse(req.body.params);
groups.updateCover({
file: req.files.files[0].path,
groupName: params.groupName
}, function(err, image) {
if (err) {
return next(err);
}
res.json([{url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
});
};
module.exports = groupsController;

@ -93,7 +93,11 @@ uploadsController.uploadThumb = function(req, res, next) {
};
uploadsController.uploadGroupCover = function(data, next) {
uploadImage(0/*req.user.uid*/, data, next);
uploadImage(0, data, next);
};
uploadsController.uploadUserCover = function(data, next) {
uploadImage(data.uid, data, next);
};
function uploadImage(uid, image, callback) {

@ -104,9 +104,7 @@ module.exports = function(Groups) {
async.parallel([
async.apply(db.setObjectField, 'group:' + groupName, 'hidden', hidden ? 1 : 0),
async.apply(updateVisibility, groupName, hidden)
], function(err, results) {
callback(err);
});
], callback);
}
Groups.updateCoverPosition = function(groupName, position, callback) {
@ -123,6 +121,10 @@ module.exports = function(Groups) {
async.series([
function(next) {
if (data.file) {
return next();
}
// Calculate md5sum of image
// This is required because user data can be private
md5sum = crypto.createHash('md5');
@ -131,6 +133,10 @@ module.exports = function(Groups) {
next();
},
function(next) {
if (data.file) {
return next();
}
// Save image
tempPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), md5sum);
var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64');
@ -142,7 +148,7 @@ module.exports = function(Groups) {
function(next) {
uploadsController.uploadGroupCover({
name: 'groupCover',
path: tempPath
path: data.file ? data.file : tempPath
}, function(err, uploadData) {
if (err) {
return next(err);
@ -156,14 +162,20 @@ module.exports = function(Groups) {
Groups.setGroupField(data.groupName, 'cover:url', url, next);
},
function(next) {
fs.unlink(tempPath, next); // Delete temporary file
fs.unlink(data.file ? data.file : tempPath, next); // Delete temporary file
}
], function(err) {
if (err) {
return callback(err);
}
Groups.updateCoverPosition(data.groupName, data.position, callback);
if (data.position) {
Groups.updateCoverPosition(data.groupName, data.position, function(err) {
callback(err, {url: url});
});
} else {
callback(err, {url: url});
}
});
};

@ -4,7 +4,6 @@ var app,
middleware = {},
nconf = require('nconf'),
async = require('async'),
path = require('path'),
winston = require('winston'),
user = require('../user'),
meta = require('../meta'),
@ -95,6 +94,12 @@ middleware.renderHeader = function(req, res, data, next) {
}
res.locals.config = results.config;
var acpPath = req.path.slice(1).split('/');
acpPath.forEach(function(path, i) {
acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1);
});
acpPath = acpPath.join(' > ');
var templateValues = {
config: results.config,
configJSON: JSON.stringify(results.config),
@ -106,7 +111,8 @@ middleware.renderHeader = function(req, res, data, next) {
authentication: results.custom_header.authentication,
scripts: results.scripts,
'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
env: process.env.NODE_ENV ? true : false
env: process.env.NODE_ENV ? true : false,
title: acpPath + ' | NodeBB Admin Control Panel'
};
templateValues.template = {name: res.locals.template};

@ -86,7 +86,7 @@ module.exports = function(Posts) {
function editMainPost(data, postData, callback) {
var tid = postData.tid;
var title = data.title.trim();
var title = data.title ? data.title.trim() : '';
async.parallel({
topic: function(next) {

@ -191,6 +191,9 @@ module.exports = function(privileges) {
};
privileges.categories.isAdminOrMod = function(cid, uid, callback) {
if (!parseInt(uid, 10)) {
return callback(null, false);
}
helpers.some([
function (next) {
user.isModerator(uid, cid, next);

@ -28,5 +28,8 @@ module.exports = function(app, middleware, controllers) {
router.post('/post/upload', middlewares, uploadsController.uploadPost);
router.post('/topic/thumb/upload', middlewares, uploadsController.uploadThumb);
router.post('/user/:userslug/uploadpicture', middlewares.concat([middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadPicture);
router.post('/user/uploadcover', middlewares.concat([middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadCoverPicture);
router.post('/groups/uploadpicture', middlewares.concat([middleware.authenticate]), controllers.groups.uploadCover);
};

@ -228,10 +228,6 @@ SocketGroups.loadMoreMembers = function(socket, data, callback) {
SocketGroups.cover = {};
SocketGroups.cover.get = function(socket, data, callback) {
groups.getGroupFields(data.groupName, ['cover:url', 'cover:position'], callback);
};
SocketGroups.cover.update = function(socket, data, callback) {
if (!socket.uid) {
return callback(new Error('[[error:no-privileges]]'));

@ -7,17 +7,14 @@ var events = require('../../events');
var websockets = require('../index');
var socketTopics = require('../topics');
var privileges = require('../../privileges');
var plugins = require('../../plugins');
var favourites = require('../../favourites');
module.exports = function(SocketPosts) {
SocketPosts.loadPostTools = function(socket, data, callback) {
if (!socket.uid) {
return;
}
if (!data) {
return callback(new Error('[[error:invalid-data]]'))
return callback(new Error('[[error:invalid-data]]'));
}
async.parallel({
@ -29,15 +26,18 @@ module.exports = function(SocketPosts) {
},
favourited: function(next) {
favourites.getFavouritesByPostIDs([data.pid], socket.uid, next);
},
tools: function(next) {
plugins.fireHook('filter:post.tools', {pid: data.pid, uid: socket.uid, tools: []}, next);
}
}, function(err, results) {
if (err) {
return callback(err);
}
results.posts.tools = []; // TODO: add filter for this
results.posts.tools = results.tools.tools;
results.posts.deleted = parseInt(results.posts.deleted, 10) === 1;
results.posts.favourited = results.favourited[0];
results.posts.selfPost = socket.uid === parseInt(results.posts.uid, 10);
results.posts.selfPost = socket.uid && socket.uid === parseInt(results.posts.uid, 10);
results.posts.display_moderator_tools = results.isAdminOrMod || results.posts.selfPost;
results.posts.display_move_tools = results.isAdminOrMod;
callback(null, results);

@ -23,6 +23,20 @@ module.exports = function(SocketUser) {
], callback);
};
SocketUser.updateCover = function(socket, data, callback) {
if (!socket.uid) {
return callback(new Error('[[error:no-privileges]]'));
}
user.isAdministrator(socket.uid, function(err, isAdmin) {
if (!isAdmin && data.uid !== socket.uid) {
return callback(new Error('[[error:no-privileges]]'));
}
user.updateCoverPicture(data, callback);
});
};
function isAdminOrSelfAndPasswordMatch(uid, data, callback) {
async.parallel({
isAdmin: async.apply(user.isAdministrator, uid),

@ -4,10 +4,12 @@ var async = require('async'),
path = require('path'),
fs = require('fs'),
nconf = require('nconf'),
crypto = require('crypto'),
winston = require('winston'),
request = require('request'),
mime = require('mime'),
uploadsController = require('../controllers/uploads'),
plugins = require('../plugins'),
file = require('../file'),
image = require('../image'),
@ -51,7 +53,7 @@ module.exports = function(User) {
next();
}
}
], function(err, result) {
], function(err) {
function done(err, image) {
if (err) {
return callback(err);
@ -99,7 +101,7 @@ module.exports = function(User) {
return callback(new Error('[[error:no-plugin]]'));
}
request.head(url, function(err, res, body) {
request.head(url, function(err, res) {
if (err) {
return callback(err);
}
@ -126,4 +128,74 @@ module.exports = function(User) {
});
});
};
User.updateCoverPosition = function(uid, position, callback) {
User.setUserField(uid, 'cover:position', position, callback);
};
User.updateCoverPicture = function(data, callback) {
var tempPath, url, md5sum;
if (!data.imageData && data.position) {
return User.updateCoverPosition(data.uid, data.position, callback);
}
async.series([
function(next) {
if (data.file) {
return next();
}
md5sum = crypto.createHash('md5');
md5sum.update(data.imageData);
md5sum = md5sum.digest('hex');
next();
},
function(next) {
if (data.file) {
return next();
}
tempPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), md5sum);
var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64');
fs.writeFile(tempPath, buffer, {
encoding: 'base64'
}, next);
},
function(next) {
uploadsController.uploadUserCover({
name: 'profileCover',
path: data.file ? data.file : tempPath,
uid: data.uid
}, function(err, uploadData) {
if (err) {
return next(err);
}
url = uploadData.url;
next();
});
},
function(next) {
User.setUserField(data.uid, 'cover:url', url, next);
},
function(next) {
require('fs').unlink(data.file ? data.file : tempPath, next);
}
], function(err) {
if (err) {
return callback(err);
}
if (data.position) {
User.updateCoverPosition(data.uid, data.position, function(err) {
callback(err, {url: url});
});
} else {
callback(err, {url: url});
}
});
};
};

@ -1,107 +1,99 @@
<div id="navigation">
<div class="col-lg-9">
<div class="panel panel-default">
<div class="panel-heading">Active Navigation</div>
<div class="panel-body">
<div class="clearfix">
<ul id="active-navigation" class="nav navbar-nav">
<!-- BEGIN navigation -->
<li data-index="{navigation.index}" class="{navigation.class} <!-- IF navigation.selected --> active <!-- ENDIF navigation.selected -->">
<a href="#" title="{navigation.route}" id="{navigation.id}">
<!-- IF navigation.iconClass -->
<i class="fa fa-fw {navigation.iconClass}"></i>
<!-- ENDIF navigation.iconClass -->
<!-- IF navigation.text -->
<span class="{navigation.textClass}">{navigation.text}</span>
<!-- ENDIF navigation.text -->
</a>
</li>
<!-- END navigation -->
</ul>
</div>
<hr/>
<ul id="enabled">
<!-- BEGIN enabled -->
<li data-index="{enabled.index}" class="well <!-- IF !enabled.selected -->hidden<!-- ENDIF !enabled.selected -->">
<form>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label>Icon:</label>
<br/>
<span class="iconPicker"><i class="fa fa-2x {enabled.iconClass}"></i>
<input class="form-control" type="hidden" name="iconClass" value="{enabled.iconClass}" />
</span>
</div>
<div class="form-group">
<label>Route:</label>
<input class="form-control" type="text" name="route" value="{enabled.route}" />
</div>
<div class="form-group">
<label>Tooltip:</label>
<input class="form-control" type="text" name="title" value="{enabled.title}" />
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label>Text:</label>
<input class="form-control" type="text" name="text" value="{enabled.text}" />
</div>
<div class="form-group">
<label>Text Class: <small>optional</small></label>
<input class="form-control" type="text" name="textClass" value="{enabled.textClass}" />
</div>
<div class="clearfix">
<ul id="active-navigation" class="nav navbar-nav">
<!-- BEGIN navigation -->
<li data-index="{navigation.index}" class="{navigation.class} <!-- IF navigation.selected --> active <!-- ENDIF navigation.selected -->">
<a href="#" title="{navigation.route}" id="{navigation.id}">
<!-- IF navigation.iconClass -->
<i class="fa fa-fw {navigation.iconClass}"></i>
<!-- ENDIF navigation.iconClass -->
<!-- IF navigation.text -->
<span class="{navigation.textClass}">{navigation.text}</span>
<!-- ENDIF navigation.text -->
</a>
</li>
<!-- END navigation -->
</ul>
</div>
<div class="form-group">
<label>ID: <small>optional</small></label>
<input class="form-control" type="text" name="id" value="{enabled.id}" />
</div>
</div>
<hr/>
<ul id="enabled">
<!-- BEGIN enabled -->
<li data-index="{enabled.index}" class="well <!-- IF !enabled.selected -->hidden<!-- ENDIF !enabled.selected -->">
<form>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label>Icon:</label>
<br/>
<span class="iconPicker"><i class="fa fa-2x {enabled.iconClass}"></i>
<input class="form-control" type="hidden" name="iconClass" value="{enabled.iconClass}" />
</span>
</div>
<hr />
<strong>Properties:</strong>
<div class="checkbox">
<label>
<input type="checkbox" name="property:adminOnly" <!-- IF enabled.properties.adminOnly -->checked<!-- ENDIF enabled.properties.adminOnly -->/> <strong>Only display to Admins</strong>
</label>
<div class="form-group">
<label>Route:</label>
<input class="form-control" type="text" name="route" value="{enabled.route}" />
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="property:loggedIn" <!-- IF enabled.properties.loggedIn -->checked<!-- ENDIF enabled.properties.loggedIn -->/> <strong>Only display to logged in users</strong>
</label>
<div class="form-group">
<label>Tooltip:</label>
<input class="form-control" type="text" name="title" value="{enabled.title}" />
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="property:targetBlank" <!-- IF enabled.properties.targetBlank -->checked<!-- ENDIF enabled.properties.targetBlank -->/> <strong>Open in a new window</strong>
</label>
</div>
<div class="col-sm-6">
<div class="form-group">
<label>Text:</label>
<input class="form-control" type="text" name="text" value="{enabled.text}" />
</div>
<div class="form-group">
<label>Text Class: <small>optional</small></label>
<input class="form-control" type="text" name="textClass" value="{enabled.textClass}" />
</div>
<hr />
<button class="btn btn-danger delete">Delete</button>
<!-- IF enabled.enabled -->
<button class="btn btn-warning toggle">Disable</button>
<!-- ELSE -->
<button class="btn btn-success toggle">Enable</button>
<!-- ENDIF enabled.enabled -->
<input type="hidden" name="enabled" value="{enabled.enabled}" />
</form>
</li>
<!-- END enabled -->
</ul>
</div>
</div>
<div class="form-group">
<label>ID: <small>optional</small></label>
<input class="form-control" type="text" name="id" value="{enabled.id}" />
</div>
</div>
</div>
<strong>Properties:</strong>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:adminOnly" <!-- IF enabled.properties.adminOnly -->checked<!-- ENDIF enabled.properties.adminOnly -->/>
<span class="mdl-switch__label"><strong>Only display to Admins</strong></span>
</label>
</div>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:loggedIn" <!-- IF enabled.properties.loggedIn -->checked<!-- ENDIF enabled.properties.loggedIn -->/> <span class="mdl-switch__label"><strong>Only display to logged in users</strong></span>
</label>
</div>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:targetBlank" <!-- IF enabled.properties.targetBlank -->checked<!-- ENDIF enabled.properties.targetBlank -->/>
<span class="mdl-switch__label"><strong>Open in a new window</strong></span>
</label>
</div>
<button class="btn btn-danger delete">Delete</button>
<!-- IF enabled.enabled -->
<button class="btn btn-warning toggle">Disable</button>
<!-- ELSE -->
<button class="btn btn-success toggle">Enable</button>
<!-- ENDIF enabled.enabled -->
<input type="hidden" name="enabled" value="{enabled.enabled}" />
</form>
</li>
<!-- END enabled -->
</ul>
</div>
<div class="col-lg-3">
@ -110,14 +102,16 @@
<div class="panel-body">
<ul id="available">
<li data-id="custom" class="clearfix">
<div data-id="custom" class="drag-item alert alert-warning pull-left">
<i class="fa fa-fw fa-navicon"></i>
<div data-id="custom" class="drag-item alert alert-success pull-left">
<i class="fa fa-fw fa-plus-circle"></i>
</div>
<strong>Custom Route</strong>
<p>
<strong>Custom Route</strong>
</p>
</li>
<!-- BEGIN available -->
<li data-id="@index" class="clearfix">
<div data-id="@index" class="drag-item alert <!-- IF available.core -->alert-info<!-- ELSE -->alert-success<!-- ENDIF available.core --> pull-left">
<div data-id="@index" class="drag-item alert <!-- IF available.core -->alert-warning<!-- ELSE -->alert-info<!-- ENDIF available.core --> pull-left">
<i class="fa fa-fw <!-- IF available.iconClass -->{available.iconClass}<!-- ELSE -->fa-navicon<!-- ENDIF available.iconClass -->"></i>
</div>
<p>

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>NodeBB Admin Control Panel</title>
<title>{title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{relative_path}/vendor/jquery/css/smoothness/jquery-ui-1.10.4.custom.min.css?{cache-buster}">

Loading…
Cancel
Save