feat: settings sorted list (#8170)

* feat: settings sorted list

see https://github.com/NodeBB/nodebb-plugin-quickstart/pull/9/files for sample
v1.18.x
Andrew Rodrigues 5 years ago committed by GitHub
parent 7cc63f7d2c
commit 3c9689a5ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,6 +10,7 @@ define('settings', function () {
'settings/array',
'settings/key',
'settings/object',
'settings/sorted-list',
];
var Settings;
@ -271,6 +272,25 @@ define('settings', function () {
onReady.push(callback);
}
},
serializeForm: function (formEl) {
var values = formEl.serializeObject();
// "Fix" checkbox values, so that unchecked options are not omitted
formEl.find('input[type="checkbox"]').each(function (idx, inputEl) {
inputEl = $(inputEl);
if (!inputEl.is(':checked')) {
values[inputEl.attr('name')] = 'off';
}
});
// save multiple selects as json arrays
formEl.find('select[multiple]').each(function (idx, selectEl) {
selectEl = $(selectEl);
values[selectEl.attr('name')] = JSON.stringify(selectEl.val());
});
return values;
},
/**
Persists the given settings with given hash.
@param hash The hash to use as settings-id.
@ -456,7 +476,6 @@ define('settings', function () {
if (err) {
return callback(err);
}
// multipe selects are saved as json arrays, parse them here
$(formEl).find('select[multiple]').each(function (idx, selectEl) {
var key = $(selectEl).attr('name');
@ -472,6 +491,12 @@ define('settings', function () {
// Save loaded settings into ajaxify.data for use client-side
ajaxify.data.settings = values;
helper.whenReady(function () {
$(formEl).find('[data-sorted-list]').each(function (idx, el) {
getHook(el, 'get').call(Settings, $(el), hash);
});
});
$(formEl).deserialize(values);
$(formEl).find('input[type="checkbox"]').each(function () {
$(this).parents('.mdl-switch').toggleClass('is-checked', $(this).is(':checked'));
@ -489,23 +514,17 @@ define('settings', function () {
},
save: function (hash, formEl, callback) {
formEl = $(formEl);
if (formEl.length) {
var values = formEl.serializeObject();
var values = helper.serializeForm(formEl);
// "Fix" checkbox values, so that unchecked options are not omitted
formEl.find('input[type="checkbox"]').each(function (idx, inputEl) {
inputEl = $(inputEl);
if (!inputEl.is(':checked')) {
values[inputEl.attr('name')] = 'off';
helper.whenReady(function () {
var list = formEl.find('[data-sorted-list]');
if (list.length) {
getHook(list, 'set').call(Settings, list, values);
}
});
// save multiple selects as json arrays
formEl.find('select[multiple]').each(function (idx, selectEl) {
selectEl = $(selectEl);
values[selectEl.attr('name')] = JSON.stringify(selectEl.val());
});
socket.emit('admin.settings.set', {
hash: hash,
values: values,

@ -0,0 +1,125 @@
'use strict';
define('settings/sorted-list', ['benchpress', 'jqueryui'], function (benchpress) {
var SortedList;
var Settings;
SortedList = {
types: ['sorted-list'],
use: function () {
Settings = this;
},
set: function ($container, values) {
var key = $container.attr('data-sorted-list');
values[key] = [];
$container.find('[data-type="item"]').each(function (idx, item) {
var itemUUID = $(item).attr('data-sorted-list-uuid');
var formData = $('[data-sorted-list-object="' + key + '"][data-sorted-list-uuid="' + itemUUID + '"]');
values[key].push(Settings.helper.serializeForm(formData));
});
},
get: function ($container) {
var $list = $container.find('[data-type="list"]');
var key = $container.attr('data-sorted-list');
var formTpl = $container.attr('data-form-template');
benchpress.parse(formTpl, {}, function (formHtml) {
var addBtn = $('[data-sorted-list="' + key + '"] [data-type="add"]');
addBtn.on('click', function () {
var modal = bootbox.confirm(formHtml, function (save) {
if (save) {
var itemUUID = utils.generateUUID();
var form = $('<form class="" data-sorted-list-uuid="' + itemUUID + '" data-sorted-list-object="' + key + '" />');
form.append(modal.find('form').children());
$('#content').append(form.hide());
var data = Settings.helper.serializeForm(form);
parse($container, itemUUID, data);
}
});
});
var list = ajaxify.data.settings[key];
if (Array.isArray(list) && typeof list[0] !== 'string') {
list.forEach(function (item) {
var itemUUID = utils.generateUUID();
var form = $(formHtml).deserialize(item);
form.attr('data-sorted-list-uuid', itemUUID);
form.attr('data-sorted-list-object', key);
$('#content').append(form.hide());
parse($container, itemUUID, item);
});
}
});
$list.sortable().addClass('pointer');
},
};
function setupRemoveButton($container, itemUUID) {
var key = $container.attr('data-sorted-list');
var removeBtn = $('[data-sorted-list="' + key + '"] [data-type="remove"]');
removeBtn.on('click', function () {
$('[data-sorted-list-uuid="' + itemUUID + '"]').remove();
});
}
function setupEditButton($container, itemUUID) {
var $list = $container.find('[data-type="list"]');
var key = $container.attr('data-sorted-list');
var itemTpl = $container.attr('data-item-template');
var editBtn = $('[data-sorted-list-uuid="' + itemUUID + '"] [data-type="edit"]');
editBtn.on('click', function () {
var form = $('[data-sorted-list-uuid="' + itemUUID + '"][data-sorted-list-object="' + key + '"]').clone(true).show();
var modal = bootbox.confirm(form, function (save) {
if (save) {
var form = $('<form class="" data-sorted-list-uuid="' + itemUUID + '" data-sorted-list-object="' + key + '" />');
form.append(modal.find('form').children());
$('#content').find('[data-sorted-list-uuid="' + itemUUID + '"][data-sorted-list-object="' + key + '"]').remove();
$('#content').append(form.hide());
var data = Settings.helper.serializeForm(form);
benchpress.parse(itemTpl, data, function (itemHtml) {
itemHtml = $(itemHtml);
var oldItem = $list.find('[data-sorted-list-uuid="' + itemUUID + '"]');
oldItem.after(itemHtml);
oldItem.remove();
itemHtml.attr('data-sorted-list-uuid', itemUUID);
setupRemoveButton($container, itemUUID);
setupEditButton($container, itemUUID);
});
}
});
});
}
function parse($container, itemUUID, data) {
var $list = $container.find('[data-type="list"]');
var itemTpl = $container.attr('data-item-template');
benchpress.parse(itemTpl, data, function (itemHtml) {
itemHtml = $(itemHtml);
$list.append(itemHtml);
itemHtml.attr('data-sorted-list-uuid', itemUUID);
setupRemoveButton($container, itemUUID);
setupEditButton($container, itemUUID);
});
}
return SortedList;
});

@ -1,73 +1,109 @@
'use strict';
var async = require('async');
const db = require('../database');
const plugins = require('../plugins');
const Meta = require('../meta');
const pubsub = require('../pubsub');
var db = require('../database');
var plugins = require('../plugins');
var Meta = require('../meta');
var pubsub = require('../pubsub');
const Settings = module.exports;
var Settings = module.exports;
Settings.get = async function (hash) {
const data = await db.getObject('settings:' + hash) || {};
const sortedLists = await db.getSetMembers('settings:' + hash + ':sorted-lists');
Settings.get = function (hash, callback) {
db.getObject('settings:' + hash, function (err, settings) {
callback(err, settings || {});
});
await Promise.all(sortedLists.map(async function (list) {
const members = await db.getSortedSetRange('settings:' + hash + ':sorted-list:' + list, 0, -1) || [];
const keys = [];
data[list] = [];
for (const order of members) {
keys.push('settings:' + hash + ':sorted-list:' + list + ':' + order);
}
const objects = await db.getObjects(keys);
objects.forEach(function (obj) {
data[list].push(obj);
});
}));
return data;
};
Settings.getOne = function (hash, field, callback) {
db.getObjectField('settings:' + hash, field, callback);
Settings.getOne = async function (hash, field) {
const data = await Settings.get(hash);
return data[field];
};
Settings.set = function (hash, values, quiet, callback) {
if (!callback && typeof quiet === 'function') {
callback = quiet;
quiet = false;
} else {
quiet = quiet || false;
Settings.set = async function (hash, values, quiet) {
quiet = quiet || false;
const sortedLists = [];
for (const key in values) {
if (values.hasOwnProperty(key)) {
if (Array.isArray(values[key]) && typeof values[key][0] !== 'string') {
sortedLists.push(key);
}
}
}
async.waterfall([
function (next) {
db.setObject('settings:' + hash, values, next);
},
function (next) {
plugins.fireHook('action:settings.set', {
plugin: hash,
settings: values,
if (sortedLists.length) {
await db.delete('settings:' + hash + ':sorted-lists');
await db.setAdd('settings:' + hash + ':sorted-lists', sortedLists);
await Promise.all(sortedLists.map(async function (list) {
await db.delete('settings:' + hash + ':sorted-list:' + list);
await Promise.all(values[list].map(async function (data, order) {
await db.delete('settings:' + hash + ':sorted-list:' + list + ':' + order);
}));
}));
const ops = [];
sortedLists.forEach(function (list) {
const arr = values[list];
delete values[list];
arr.forEach(function (data, order) {
ops.push(db.sortedSetAdd('settings:' + hash + ':sorted-list:' + list, order, order));
ops.push(db.setObject('settings:' + hash + ':sorted-list:' + list + ':' + order, data));
});
pubsub.publish('action:settings.set.' + hash, values);
Meta.reloadRequired = !quiet;
next();
},
], callback);
});
await Promise.all(ops);
}
if (Object.keys(values).length) {
await db.setObject('settings:' + hash, values);
}
plugins.fireHook('action:settings.set', {
plugin: hash,
settings: values,
});
pubsub.publish('action:settings.set.' + hash, values);
Meta.reloadRequired = !quiet;
};
Settings.setOne = function (hash, field, value, callback) {
var data = {};
Settings.setOne = async function (hash, field, value) {
const data = {};
data[field] = value;
Settings.set(hash, data, callback);
await Settings.set(hash, data);
};
Settings.setOnEmpty = function (hash, values, callback) {
async.waterfall([
function (next) {
db.getObject('settings:' + hash, next);
},
function (settings, next) {
settings = settings || {};
var empty = {};
Object.keys(values).forEach(function (key) {
if (!settings.hasOwnProperty(key)) {
empty[key] = values[key];
}
});
Settings.setOnEmpty = async function (hash, values) {
const settings = await Settings.get(hash) || {};
const empty = {};
if (Object.keys(empty).length) {
Settings.set(hash, empty, next);
} else {
next();
}
},
], callback);
Object.keys(values).forEach(function (key) {
if (!settings.hasOwnProperty(key)) {
empty[key] = values[key];
}
});
if (Object.keys(empty).length) {
await Settings.set(hash, empty);
}
};

@ -93,6 +93,91 @@ describe('meta', function () {
});
});
});
const someList = [
{ name: 'andrew', status: 'best' },
{ name: 'baris', status: 'wurst' },
];
const anotherList = [];
it('should set setting with sorted list', function (done) {
socketAdmin.settings.set({ uid: fooUid }, { hash: 'another:hash', values: { foo: '1', derp: 'value', someList: someList, anotherList: anotherList } }, function (err) {
if (err) {
return done(err);
}
db.getObject('settings:another:hash', function (err, data) {
if (err) {
return done(err);
}
assert.equal(data.foo, '1');
assert.equal(data.derp, 'value');
assert.equal(data.someList, undefined);
assert.equal(data.anotherList, undefined);
done();
});
});
});
it('should get setting with sorted list', function (done) {
socketAdmin.settings.get({ uid: fooUid }, { hash: 'another:hash' }, function (err, data) {
assert.ifError(err);
assert.equal(data.foo, '1');
assert.equal(data.derp, 'value');
assert.deepEqual(data.someList, someList);
assert.deepEqual(data.anotherList, anotherList);
done();
});
});
it('should not set setting if not empty', function (done) {
meta.settings.setOnEmpty('some:hash', { foo: 2 }, function (err) {
assert.ifError(err);
db.getObject('settings:some:hash', function (err, data) {
assert.ifError(err);
assert.equal(data.foo, '1');
assert.equal(data.derp, 'value');
done();
});
});
});
it('should not set setting with sorted list if not empty', function (done) {
meta.settings.setOnEmpty('another:hash', { foo: anotherList }, function (err) {
assert.ifError(err);
socketAdmin.settings.get({ uid: fooUid }, { hash: 'another:hash' }, function (err, data) {
assert.ifError(err);
assert.equal(data.foo, '1');
assert.equal(data.derp, 'value');
done();
});
});
});
it('should set setting with sorted list if empty', function (done) {
meta.settings.setOnEmpty('another:hash', { empty: someList }, function (err) {
assert.ifError(err);
socketAdmin.settings.get({ uid: fooUid }, { hash: 'another:hash' }, function (err, data) {
assert.ifError(err);
assert.equal(data.foo, '1');
assert.equal(data.derp, 'value');
assert.deepEqual(data.empty, someList);
done();
});
});
});
it('should set one and get one sorted list', function (done) {
meta.settings.setOne('another:hash', 'someList', someList, function (err) {
assert.ifError(err);
meta.settings.getOne('another:hash', 'someList', function (err, _someList) {
assert.ifError(err);
assert.deepEqual(_someList, someList);
done();
});
});
});
});

Loading…
Cancel
Save