mirror of
https://github.com/wekan/wekan.git
synced 2026-03-13 17:06:13 +01:00
Migrate sidebar and settings components from BlazeComponent to Template
Convert sidebar, sidebarArchives, sidebarCustomFields, sidebarFilters, sidebarSearches, and all settings panel components to use native Meteor Template.onCreated/helpers/events pattern.
This commit is contained in:
parent
d3625db755
commit
bae23f9ed8
12 changed files with 2937 additions and 3046 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,28 +1,84 @@
|
|||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import Lists from '/models/lists';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
|
||||
//archivedRequested = false;
|
||||
const subManager = new SubsManager();
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.isArchiveReady = new ReactiveVar(false);
|
||||
function getArchivedCards() {
|
||||
const ret = ReactiveCache.getCards(
|
||||
{
|
||||
archived: true,
|
||||
boardId: Session.get('currentBoard'),
|
||||
},
|
||||
{
|
||||
sort: { archivedAt: -1, modifiedAt: -1 },
|
||||
},
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// The pattern we use to manually handle data loading is described here:
|
||||
// https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager
|
||||
// XXX The boardId should be readed from some sort the component "props",
|
||||
// unfortunatly, Blaze doesn't have this notion.
|
||||
this.autorun(() => {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
const handle = subManager.subscribe('board', currentBoardId, true);
|
||||
//archivedRequested = true;
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
this.isArchiveReady.set(handle.ready());
|
||||
});
|
||||
function getArchivedLists() {
|
||||
return ReactiveCache.getLists(
|
||||
{
|
||||
archived: true,
|
||||
boardId: Session.get('currentBoard'),
|
||||
},
|
||||
{
|
||||
sort: { archivedAt: -1, modifiedAt: -1 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function getArchivedSwimlanes() {
|
||||
return ReactiveCache.getSwimlanes(
|
||||
{
|
||||
archived: true,
|
||||
boardId: Session.get('currentBoard'),
|
||||
},
|
||||
{
|
||||
sort: { archivedAt: -1, modifiedAt: -1 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Template.archivesSidebar.onCreated(function () {
|
||||
this.isArchiveReady = new ReactiveVar(false);
|
||||
|
||||
// The pattern we use to manually handle data loading is described here:
|
||||
// https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager
|
||||
// XXX The boardId should be readed from some sort the component "props",
|
||||
// unfortunatly, Blaze doesn't have this notion.
|
||||
this.autorun(() => {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
const handle = subManager.subscribe('board', currentBoardId, true);
|
||||
//archivedRequested = true;
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
this.isArchiveReady.set(handle.ready());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Template.archivesSidebar.onRendered(function () {
|
||||
// XXX We should support dragging a card from the sidebar to the board
|
||||
});
|
||||
|
||||
Template.archivesSidebar.helpers({
|
||||
isArchiveReady() {
|
||||
return Template.instance().isArchiveReady;
|
||||
},
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
},
|
||||
isWorker() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
return (
|
||||
!currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
|
||||
);
|
||||
},
|
||||
|
||||
tabs() {
|
||||
|
|
@ -34,139 +90,98 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
archivedCards() {
|
||||
const ret = ReactiveCache.getCards(
|
||||
{
|
||||
archived: true,
|
||||
boardId: Session.get('currentBoard'),
|
||||
},
|
||||
{
|
||||
sort: { archivedAt: -1, modifiedAt: -1 },
|
||||
},
|
||||
);
|
||||
return ret;
|
||||
return getArchivedCards();
|
||||
},
|
||||
|
||||
archivedLists() {
|
||||
return ReactiveCache.getLists(
|
||||
{
|
||||
archived: true,
|
||||
boardId: Session.get('currentBoard'),
|
||||
},
|
||||
{
|
||||
sort: { archivedAt: -1, modifiedAt: -1 },
|
||||
},
|
||||
);
|
||||
return getArchivedLists();
|
||||
},
|
||||
|
||||
archivedSwimlanes() {
|
||||
return ReactiveCache.getSwimlanes(
|
||||
{
|
||||
archived: true,
|
||||
boardId: Session.get('currentBoard'),
|
||||
},
|
||||
{
|
||||
sort: { archivedAt: -1, modifiedAt: -1 },
|
||||
},
|
||||
);
|
||||
return getArchivedSwimlanes();
|
||||
},
|
||||
|
||||
cardIsInArchivedList() {
|
||||
return this.currentData().list().archived;
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
// XXX We should support dragging a card from the sidebar to the board
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
async 'click .js-restore-card'() {
|
||||
const card = this.currentData();
|
||||
if (card.canBeRestored()) {
|
||||
await card.restore();
|
||||
}
|
||||
},
|
||||
async 'click .js-restore-all-cards'() {
|
||||
for (const card of this.archivedCards()) {
|
||||
if (card.canBeRestored()) {
|
||||
await card.restore();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-delete-card': Popup.afterConfirm('cardDelete', async function() {
|
||||
const cardId = this._id;
|
||||
await Cards.removeAsync(cardId);
|
||||
Popup.back();
|
||||
}),
|
||||
'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', async () => {
|
||||
for (const card of this.archivedCards()) {
|
||||
await Cards.removeAsync(card._id);
|
||||
}
|
||||
Popup.back();
|
||||
}),
|
||||
|
||||
async 'click .js-restore-list'() {
|
||||
const list = this.currentData();
|
||||
await list.restore();
|
||||
},
|
||||
async 'click .js-restore-all-lists'() {
|
||||
for (const list of this.archivedLists()) {
|
||||
await list.restore();
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-delete-list': Popup.afterConfirm('listDelete', async function() {
|
||||
await this.remove();
|
||||
Popup.back();
|
||||
}),
|
||||
'click .js-delete-all-lists': Popup.afterConfirm('listDelete', async () => {
|
||||
for (const list of this.archivedLists()) {
|
||||
await list.remove();
|
||||
}
|
||||
Popup.back();
|
||||
}),
|
||||
|
||||
async 'click .js-restore-swimlane'() {
|
||||
const swimlane = this.currentData();
|
||||
await swimlane.restore();
|
||||
},
|
||||
async 'click .js-restore-all-swimlanes'() {
|
||||
for (const swimlane of this.archivedSwimlanes()) {
|
||||
await swimlane.restore();
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-delete-swimlane': Popup.afterConfirm(
|
||||
'swimlaneDelete',
|
||||
async function() {
|
||||
await this.remove();
|
||||
Popup.back();
|
||||
},
|
||||
),
|
||||
'click .js-delete-all-swimlanes': Popup.afterConfirm(
|
||||
'swimlaneDelete',
|
||||
async () => {
|
||||
for (const swimlane of this.archivedSwimlanes()) {
|
||||
await swimlane.remove();
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('archivesSidebar');
|
||||
|
||||
Template.archivesSidebar.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
},
|
||||
isWorker() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
return (
|
||||
!currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
|
||||
);
|
||||
return Template.currentData().list().archived;
|
||||
},
|
||||
});
|
||||
|
||||
Template.archivesSidebar.events({
|
||||
async 'click .js-restore-card'() {
|
||||
const card = Template.currentData();
|
||||
if (card.canBeRestored()) {
|
||||
await card.restore();
|
||||
}
|
||||
},
|
||||
async 'click .js-restore-all-cards'() {
|
||||
for (const card of getArchivedCards()) {
|
||||
if (card.canBeRestored()) {
|
||||
await card.restore();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-delete-card': Popup.afterConfirm('cardDelete', async function() {
|
||||
const cardId = this._id;
|
||||
await Cards.removeAsync(cardId);
|
||||
Popup.back();
|
||||
}),
|
||||
'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', async () => {
|
||||
for (const card of getArchivedCards()) {
|
||||
await Cards.removeAsync(card._id);
|
||||
}
|
||||
Popup.back();
|
||||
}),
|
||||
|
||||
async 'click .js-restore-list'() {
|
||||
const data = Template.currentData();
|
||||
const list = Lists.findOne(data._id) || data;
|
||||
await list.restore();
|
||||
},
|
||||
async 'click .js-restore-all-lists'() {
|
||||
for (const list of getArchivedLists()) {
|
||||
await list.restore();
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-delete-list': Popup.afterConfirm('listDelete', async function() {
|
||||
const list = Lists.findOne(this._id);
|
||||
if (list) await list.remove();
|
||||
Popup.back();
|
||||
}),
|
||||
'click .js-delete-all-lists': Popup.afterConfirm('listDelete', async () => {
|
||||
for (const list of getArchivedLists()) {
|
||||
await list.remove();
|
||||
}
|
||||
Popup.back();
|
||||
}),
|
||||
|
||||
async 'click .js-restore-swimlane'() {
|
||||
const data = Template.currentData();
|
||||
const swimlane = Swimlanes.findOne(data._id) || data;
|
||||
await swimlane.restore();
|
||||
},
|
||||
async 'click .js-restore-all-swimlanes'() {
|
||||
for (const swimlane of getArchivedSwimlanes()) {
|
||||
await swimlane.restore();
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-delete-swimlane': Popup.afterConfirm(
|
||||
'swimlaneDelete',
|
||||
async function() {
|
||||
const swimlane = Swimlanes.findOne(this._id);
|
||||
if (swimlane) await swimlane.remove();
|
||||
Popup.back();
|
||||
},
|
||||
),
|
||||
'click .js-delete-all-swimlanes': Popup.afterConfirm(
|
||||
'swimlaneDelete',
|
||||
async () => {
|
||||
for (const swimlane of getArchivedSwimlanes()) {
|
||||
await swimlane.remove();
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,111 +1,119 @@
|
|||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
Template.customFieldsSidebar.helpers({
|
||||
customFields() {
|
||||
const ret = ReactiveCache.getCustomFields({
|
||||
boardIds: { $in: [Session.get('currentBoard')] },
|
||||
});
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-open-create-custom-field': Popup.open('createCustomField'),
|
||||
'click .js-edit-custom-field': Popup.open('editCustomField'),
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('customFieldsSidebar');
|
||||
Template.customFieldsSidebar.events({
|
||||
'click .js-open-create-custom-field': Popup.open('createCustomField'),
|
||||
'click .js-edit-custom-field': Popup.open('editCustomField'),
|
||||
});
|
||||
|
||||
const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
_types: [
|
||||
'text',
|
||||
'number',
|
||||
'date',
|
||||
'dropdown',
|
||||
'currency',
|
||||
'checkbox',
|
||||
'stringtemplate',
|
||||
],
|
||||
const CUSTOM_FIELD_TYPES = [
|
||||
'text',
|
||||
'number',
|
||||
'date',
|
||||
'dropdown',
|
||||
'currency',
|
||||
'checkbox',
|
||||
'stringtemplate',
|
||||
];
|
||||
|
||||
_currencyList: [
|
||||
{
|
||||
name: 'US Dollar',
|
||||
code: 'USD',
|
||||
},
|
||||
{
|
||||
name: 'Euro',
|
||||
code: 'EUR',
|
||||
},
|
||||
{
|
||||
name: 'Yen',
|
||||
code: 'JPY',
|
||||
},
|
||||
{
|
||||
name: 'Pound Sterling',
|
||||
code: 'GBP',
|
||||
},
|
||||
{
|
||||
name: 'Australian Dollar',
|
||||
code: 'AUD',
|
||||
},
|
||||
{
|
||||
name: 'Canadian Dollar',
|
||||
code: 'CAD',
|
||||
},
|
||||
{
|
||||
name: 'Swiss Franc',
|
||||
code: 'CHF',
|
||||
},
|
||||
{
|
||||
name: 'Yuan Renminbi',
|
||||
code: 'CNY',
|
||||
},
|
||||
{
|
||||
name: 'Hong Kong Dollar',
|
||||
code: 'HKD',
|
||||
},
|
||||
{
|
||||
name: 'New Zealand Dollar',
|
||||
code: 'NZD',
|
||||
},
|
||||
],
|
||||
const CURRENCY_LIST = [
|
||||
{ name: 'US Dollar', code: 'USD' },
|
||||
{ name: 'Euro', code: 'EUR' },
|
||||
{ name: 'Yen', code: 'JPY' },
|
||||
{ name: 'Pound Sterling', code: 'GBP' },
|
||||
{ name: 'Australian Dollar', code: 'AUD' },
|
||||
{ name: 'Canadian Dollar', code: 'CAD' },
|
||||
{ name: 'Swiss Franc', code: 'CHF' },
|
||||
{ name: 'Yuan Renminbi', code: 'CNY' },
|
||||
{ name: 'Hong Kong Dollar', code: 'HKD' },
|
||||
{ name: 'New Zealand Dollar', code: 'NZD' },
|
||||
];
|
||||
|
||||
onCreated() {
|
||||
this.type = new ReactiveVar(
|
||||
this.data().type ? this.data().type : this._types[0],
|
||||
);
|
||||
function getDropdownItems(tpl) {
|
||||
const items = tpl.dropdownItems.get();
|
||||
Array.from(tpl.findAll('.js-field-settings-dropdown input')).forEach(
|
||||
(el, index) => {
|
||||
if (!items[index])
|
||||
items[index] = {
|
||||
_id: Random.id(6),
|
||||
};
|
||||
items[index].name = el.value.trim();
|
||||
},
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
this.currencyCode = new ReactiveVar(
|
||||
this.data().settings && this.data().settings.currencyCode
|
||||
? this.data().settings.currencyCode
|
||||
: this._currencyList[0].code,
|
||||
);
|
||||
function getSettings(tpl) {
|
||||
const settings = {};
|
||||
switch (tpl.type.get()) {
|
||||
case 'currency': {
|
||||
const currencyCode = tpl.currencyCode.get();
|
||||
settings.currencyCode = currencyCode;
|
||||
break;
|
||||
}
|
||||
case 'dropdown': {
|
||||
const dropdownItems = getDropdownItems(tpl).filter(
|
||||
item => !!item.name.trim(),
|
||||
);
|
||||
settings.dropdownItems = dropdownItems;
|
||||
break;
|
||||
}
|
||||
case 'stringtemplate': {
|
||||
const stringtemplateFormat = tpl.stringtemplateFormat.get();
|
||||
settings.stringtemplateFormat = stringtemplateFormat;
|
||||
|
||||
this.dropdownItems = new ReactiveVar(
|
||||
this.data().settings && this.data().settings.dropdownItems
|
||||
? this.data().settings.dropdownItems
|
||||
: [],
|
||||
);
|
||||
const stringtemplateSeparator = tpl.stringtemplateSeparator.get();
|
||||
settings.stringtemplateSeparator = stringtemplateSeparator;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
this.stringtemplateFormat = new ReactiveVar(
|
||||
this.data().settings && this.data().settings.stringtemplateFormat
|
||||
? this.data().settings.stringtemplateFormat
|
||||
: '',
|
||||
);
|
||||
Template.createCustomFieldPopup.onCreated(function () {
|
||||
const data = Template.currentData();
|
||||
this.type = new ReactiveVar(
|
||||
data.type ? data.type : CUSTOM_FIELD_TYPES[0],
|
||||
);
|
||||
|
||||
this.stringtemplateSeparator = new ReactiveVar(
|
||||
this.data().settings && this.data().settings.stringtemplateSeparator
|
||||
? this.data().settings.stringtemplateSeparator
|
||||
: '',
|
||||
);
|
||||
},
|
||||
this.currencyCode = new ReactiveVar(
|
||||
data.settings && data.settings.currencyCode
|
||||
? data.settings.currencyCode
|
||||
: CURRENCY_LIST[0].code,
|
||||
);
|
||||
|
||||
this.dropdownItems = new ReactiveVar(
|
||||
data.settings && data.settings.dropdownItems
|
||||
? data.settings.dropdownItems
|
||||
: [],
|
||||
);
|
||||
|
||||
this.stringtemplateFormat = new ReactiveVar(
|
||||
data.settings && data.settings.stringtemplateFormat
|
||||
? data.settings.stringtemplateFormat
|
||||
: '',
|
||||
);
|
||||
|
||||
this.stringtemplateSeparator = new ReactiveVar(
|
||||
data.settings && data.settings.stringtemplateSeparator
|
||||
? data.settings.stringtemplateSeparator
|
||||
: '',
|
||||
);
|
||||
});
|
||||
|
||||
Template.createCustomFieldPopup.helpers({
|
||||
types() {
|
||||
const currentType = this.data().type;
|
||||
return this._types.map(type => {
|
||||
const currentType = Template.currentData().type;
|
||||
return CUSTOM_FIELD_TYPES.map(type => {
|
||||
return {
|
||||
value: type,
|
||||
name: TAPi18n.__(`custom-field-${type}`),
|
||||
|
|
@ -115,13 +123,13 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
isTypeNotSelected(type) {
|
||||
return this.type.get() !== type;
|
||||
return Template.instance().type.get() !== type;
|
||||
},
|
||||
|
||||
getCurrencyCodes() {
|
||||
const currentCode = this.currencyCode.get();
|
||||
const currentCode = Template.instance().currencyCode.get();
|
||||
|
||||
return this._currencyList.map(({ name, code }) => {
|
||||
return CURRENCY_LIST.map(({ name, code }) => {
|
||||
return {
|
||||
name: `${code} - ${name}`,
|
||||
value: code,
|
||||
|
|
@ -131,176 +139,128 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
getDropdownItems() {
|
||||
const items = this.dropdownItems.get();
|
||||
Array.from(this.findAll('.js-field-settings-dropdown input')).forEach(
|
||||
(el, index) => {
|
||||
//console.log('each item!', index, el.value);
|
||||
if (!items[index])
|
||||
items[index] = {
|
||||
_id: Random.id(6),
|
||||
};
|
||||
items[index].name = el.value.trim();
|
||||
},
|
||||
);
|
||||
return items;
|
||||
return getDropdownItems(Template.instance());
|
||||
},
|
||||
|
||||
getStringtemplateFormat() {
|
||||
return this.stringtemplateFormat.get();
|
||||
return Template.instance().stringtemplateFormat.get();
|
||||
},
|
||||
|
||||
getStringtemplateSeparator() {
|
||||
return this.stringtemplateSeparator.get();
|
||||
},
|
||||
|
||||
getSettings() {
|
||||
const settings = {};
|
||||
switch (this.type.get()) {
|
||||
case 'currency': {
|
||||
const currencyCode = this.currencyCode.get();
|
||||
settings.currencyCode = currencyCode;
|
||||
break;
|
||||
}
|
||||
case 'dropdown': {
|
||||
const dropdownItems = this.getDropdownItems().filter(
|
||||
item => !!item.name.trim(),
|
||||
);
|
||||
settings.dropdownItems = dropdownItems;
|
||||
break;
|
||||
}
|
||||
case 'stringtemplate': {
|
||||
const stringtemplateFormat = this.stringtemplateFormat.get();
|
||||
settings.stringtemplateFormat = stringtemplateFormat;
|
||||
|
||||
const stringtemplateSeparator = this.stringtemplateSeparator.get();
|
||||
settings.stringtemplateSeparator = stringtemplateSeparator;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'change .js-field-type'(evt) {
|
||||
const value = evt.target.value;
|
||||
this.type.set(value);
|
||||
},
|
||||
'change .js-field-currency'(evt) {
|
||||
const value = evt.target.value;
|
||||
this.currencyCode.set(value);
|
||||
},
|
||||
'keydown .js-dropdown-item.last'(evt) {
|
||||
if (evt.target.value.trim() && evt.keyCode === 13) {
|
||||
const items = this.getDropdownItems();
|
||||
this.dropdownItems.set(items);
|
||||
evt.target.value = '';
|
||||
}
|
||||
},
|
||||
'input .js-field-stringtemplate-format'(evt) {
|
||||
const value = evt.target.value;
|
||||
this.stringtemplateFormat.set(value);
|
||||
},
|
||||
'input .js-field-stringtemplate-separator'(evt) {
|
||||
const value = evt.target.value;
|
||||
this.stringtemplateSeparator.set(value);
|
||||
},
|
||||
'click .js-field-show-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-show-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-automatically-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-automatically-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-always-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-always-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-showLabel-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-showLabel-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-show-sum-at-top-of-list'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-show-sum-at-top-of-list')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .primary'(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const data = {
|
||||
name: this.find('.js-field-name').value.trim(),
|
||||
type: this.type.get(),
|
||||
settings: this.getSettings(),
|
||||
showOnCard: this.find('.js-field-show-on-card.is-checked') !== null,
|
||||
showLabelOnMiniCard:
|
||||
this.find('.js-field-showLabel-on-card.is-checked') !== null,
|
||||
automaticallyOnCard:
|
||||
this.find('.js-field-automatically-on-card.is-checked') !== null,
|
||||
alwaysOnCard:
|
||||
this.find('.js-field-always-on-card.is-checked') !== null,
|
||||
showSumAtTopOfList:
|
||||
this.find('.js-field-show-sum-at-top-of-list.is-checked') !== null,
|
||||
};
|
||||
|
||||
// insert or update
|
||||
if (!this.data()._id) {
|
||||
data.boardIds = [Session.get('currentBoard')];
|
||||
CustomFields.insert(data);
|
||||
} else {
|
||||
CustomFields.update(this.data()._id, { $set: data });
|
||||
}
|
||||
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-delete-custom-field': Popup.afterConfirm(
|
||||
'deleteCustomField',
|
||||
function() {
|
||||
const customField = ReactiveCache.getCustomField(this._id);
|
||||
if (customField.boardIds.length > 1) {
|
||||
CustomFields.update(customField._id, {
|
||||
$pull: {
|
||||
boardIds: Session.get('currentBoard'),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
CustomFields.remove(customField._id);
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
),
|
||||
},
|
||||
];
|
||||
return Template.instance().stringtemplateSeparator.get();
|
||||
},
|
||||
});
|
||||
CreateCustomFieldPopup.register('createCustomFieldPopup');
|
||||
|
||||
(class extends CreateCustomFieldPopup {
|
||||
template() {
|
||||
return 'createCustomFieldPopup';
|
||||
}
|
||||
}.register('editCustomFieldPopup'));
|
||||
Template.createCustomFieldPopup.events({
|
||||
'change .js-field-type'(evt, tpl) {
|
||||
const value = evt.target.value;
|
||||
tpl.type.set(value);
|
||||
},
|
||||
'change .js-field-currency'(evt, tpl) {
|
||||
const value = evt.target.value;
|
||||
tpl.currencyCode.set(value);
|
||||
},
|
||||
'keydown .js-dropdown-item.last'(evt, tpl) {
|
||||
if (evt.target.value.trim() && evt.keyCode === 13) {
|
||||
const items = getDropdownItems(tpl);
|
||||
tpl.dropdownItems.set(items);
|
||||
evt.target.value = '';
|
||||
}
|
||||
},
|
||||
'input .js-field-stringtemplate-format'(evt, tpl) {
|
||||
const value = evt.target.value;
|
||||
tpl.stringtemplateFormat.set(value);
|
||||
},
|
||||
'input .js-field-stringtemplate-separator'(evt, tpl) {
|
||||
const value = evt.target.value;
|
||||
tpl.stringtemplateSeparator.set(value);
|
||||
},
|
||||
'click .js-field-show-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-show-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-automatically-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-automatically-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-always-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-always-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-showLabel-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-showLabel-on-card')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .js-field-show-sum-at-top-of-list'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-show-sum-at-top-of-list')) {
|
||||
$target = $target.parent();
|
||||
}
|
||||
$target.find('.materialCheckBox').toggleClass('is-checked');
|
||||
$target.toggleClass('is-checked');
|
||||
},
|
||||
'click .primary'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
|
||||
const data = {
|
||||
name: tpl.find('.js-field-name').value.trim(),
|
||||
type: tpl.type.get(),
|
||||
settings: getSettings(tpl),
|
||||
showOnCard: tpl.find('.js-field-show-on-card.is-checked') !== null,
|
||||
showLabelOnMiniCard:
|
||||
tpl.find('.js-field-showLabel-on-card.is-checked') !== null,
|
||||
automaticallyOnCard:
|
||||
tpl.find('.js-field-automatically-on-card.is-checked') !== null,
|
||||
alwaysOnCard:
|
||||
tpl.find('.js-field-always-on-card.is-checked') !== null,
|
||||
showSumAtTopOfList:
|
||||
tpl.find('.js-field-show-sum-at-top-of-list.is-checked') !== null,
|
||||
};
|
||||
|
||||
const currentData = Template.currentData();
|
||||
// insert or update
|
||||
if (!currentData._id) {
|
||||
data.boardIds = [Session.get('currentBoard')];
|
||||
CustomFields.insert(data);
|
||||
} else {
|
||||
CustomFields.update(currentData._id, { $set: data });
|
||||
}
|
||||
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-delete-custom-field': Popup.afterConfirm(
|
||||
'deleteCustomField',
|
||||
function() {
|
||||
const customField = ReactiveCache.getCustomField(this._id);
|
||||
if (customField.boardIds.length > 1) {
|
||||
CustomFields.update(customField._id, {
|
||||
$pull: {
|
||||
boardIds: Session.get('currentBoard'),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
CustomFields.remove(customField._id);
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
/*Template.deleteCustomFieldPopup.events({
|
||||
'submit'(evt) {
|
||||
|
|
|
|||
|
|
@ -3,108 +3,102 @@ import { TAPi18n } from '/imports/i18n';
|
|||
|
||||
const subManager = new SubsManager();
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'submit .js-list-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.lists.set(this.find('.js-list-filter input').value.trim());
|
||||
},
|
||||
'change .js-field-card-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.title.set(this.find('.js-field-card-filter').value.trim());
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-label-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.labelIds.toggle(this.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-member-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.members.toggle(this.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-assignee-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.assignees.toggle(this.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-no-due-date-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.noDate();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-overdue-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.past();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-today-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.today();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-tomorrow-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.tomorrow();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-this-week-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.thisWeek();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-next-week-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.nextWeek();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-archive-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.archive.toggle(this.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
subManager.subscribe(
|
||||
'board',
|
||||
currentBoardId,
|
||||
Filter.archive.isSelected(),
|
||||
);
|
||||
},
|
||||
'click .js-toggle-hideEmpty-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.hideEmpty.toggle(this.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-custom-fields-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.customFields.toggle(this.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'change .js-field-advanced-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.advanced.set(
|
||||
this.find('.js-field-advanced-filter').value.trim(),
|
||||
);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-clear-all'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.reset();
|
||||
},
|
||||
'click .js-filter-to-selection'(evt) {
|
||||
evt.preventDefault();
|
||||
const selectedCards = ReactiveCache.getCards(Filter.mongoSelector()).map(c => {
|
||||
return c._id;
|
||||
});
|
||||
MultiSelection.add(selectedCards);
|
||||
},
|
||||
},
|
||||
];
|
||||
Template.filterSidebar.events({
|
||||
'submit .js-list-filter'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
Filter.lists.set(tpl.find('.js-list-filter input').value.trim());
|
||||
},
|
||||
}).register('filterSidebar');
|
||||
'change .js-field-card-filter'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
Filter.title.set(tpl.find('.js-field-card-filter').value.trim());
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-label-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.labelIds.toggle(Template.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-member-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.members.toggle(Template.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-assignee-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.assignees.toggle(Template.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-no-due-date-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.noDate();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-overdue-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.past();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-today-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.today();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-tomorrow-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.tomorrow();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-this-week-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.thisWeek();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-next-week-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.nextWeek();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-archive-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.archive.toggle(Template.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
subManager.subscribe(
|
||||
'board',
|
||||
currentBoardId,
|
||||
Filter.archive.isSelected(),
|
||||
);
|
||||
},
|
||||
'click .js-toggle-hideEmpty-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.hideEmpty.toggle(Template.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-custom-fields-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.customFields.toggle(Template.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'change .js-field-advanced-filter'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
Filter.advanced.set(
|
||||
tpl.find('.js-field-advanced-filter').value.trim(),
|
||||
);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-clear-all'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.reset();
|
||||
},
|
||||
'click .js-filter-to-selection'(evt) {
|
||||
evt.preventDefault();
|
||||
const selectedCards = ReactiveCache.getCards(Filter.mongoSelector()).map(c => {
|
||||
return c._id;
|
||||
});
|
||||
MultiSelection.add(selectedCards);
|
||||
},
|
||||
});
|
||||
|
||||
async function mutateSelectedCards(mutationNameOrCallback, ...args) {
|
||||
const cards = ReactiveCache.getCards(MultiSelection.getMongoSelector(), {sort: ['sort']});
|
||||
|
|
@ -181,67 +175,12 @@ function buildInsertionSortIndexes(cardsCount, targetCard, position, listId, swi
|
|||
return indexes;
|
||||
}
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
mapSelection(kind, _id) {
|
||||
return ReactiveCache.getCards(MultiSelection.getMongoSelector(), {sort: ['sort']}).map(card => {
|
||||
const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned';
|
||||
return card[methodName](_id);
|
||||
});
|
||||
},
|
||||
|
||||
allSelectedElementHave(kind, _id) {
|
||||
if (MultiSelection.isEmpty()) return false;
|
||||
else return _.every(this.mapSelection(kind, _id));
|
||||
},
|
||||
|
||||
someSelectedElementHave(kind, _id) {
|
||||
if (MultiSelection.isEmpty()) return false;
|
||||
else return _.some(this.mapSelection(kind, _id));
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-toggle-label-multiselection'(evt) {
|
||||
const labelId = this.currentData()._id;
|
||||
const mappedSelection = this.mapSelection('label', labelId);
|
||||
|
||||
if (_.every(mappedSelection)) {
|
||||
mutateSelectedCards('removeLabel', labelId);
|
||||
} else if (_.every(mappedSelection, bool => !bool)) {
|
||||
mutateSelectedCards('addLabel', labelId);
|
||||
} else {
|
||||
const popup = Popup.open('disambiguateMultiLabel');
|
||||
// XXX We need to have a better integration between the popup and the
|
||||
// UI components systems.
|
||||
popup.call(this.currentData(), evt);
|
||||
}
|
||||
},
|
||||
'click .js-toggle-member-multiselection'(evt) {
|
||||
const memberId = this.currentData()._id;
|
||||
const mappedSelection = this.mapSelection('member', memberId);
|
||||
if (_.every(mappedSelection)) {
|
||||
mutateSelectedCards('unassignMember', memberId);
|
||||
} else if (_.every(mappedSelection, bool => !bool)) {
|
||||
mutateSelectedCards('assignMember', memberId);
|
||||
} else {
|
||||
const popup = Popup.open('disambiguateMultiMember');
|
||||
// XXX We need to have a better integration between the popup and the
|
||||
// UI components systems.
|
||||
popup.call(this.currentData(), evt);
|
||||
}
|
||||
},
|
||||
'click .js-move-selection': Popup.open('moveSelection'),
|
||||
'click .js-copy-selection': Popup.open('copySelection'),
|
||||
'click .js-selection-color': Popup.open('setSelectionColor'),
|
||||
'click .js-archive-selection'() {
|
||||
mutateSelectedCards('archive');
|
||||
EscapeActions.executeUpTo('multiselection');
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('multiselectionSidebar');
|
||||
function mapSelection(kind, _id) {
|
||||
return ReactiveCache.getCards(MultiSelection.getMongoSelector(), {sort: ['sort']}).map(card => {
|
||||
const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned';
|
||||
return card[methodName](_id);
|
||||
});
|
||||
}
|
||||
|
||||
Template.multiselectionSidebar.helpers({
|
||||
isBoardAdmin() {
|
||||
|
|
@ -250,6 +189,53 @@ Template.multiselectionSidebar.helpers({
|
|||
isCommentOnly() {
|
||||
return ReactiveCache.getCurrentUser().isCommentOnly();
|
||||
},
|
||||
allSelectedElementHave(kind, _id) {
|
||||
if (MultiSelection.isEmpty()) return false;
|
||||
else return _.every(mapSelection(kind, _id));
|
||||
},
|
||||
someSelectedElementHave(kind, _id) {
|
||||
if (MultiSelection.isEmpty()) return false;
|
||||
else return _.some(mapSelection(kind, _id));
|
||||
},
|
||||
});
|
||||
|
||||
Template.multiselectionSidebar.events({
|
||||
'click .js-toggle-label-multiselection'(evt) {
|
||||
const labelId = Template.currentData()._id;
|
||||
const mappedSelection = mapSelection('label', labelId);
|
||||
|
||||
if (_.every(mappedSelection)) {
|
||||
mutateSelectedCards('removeLabel', labelId);
|
||||
} else if (_.every(mappedSelection, bool => !bool)) {
|
||||
mutateSelectedCards('addLabel', labelId);
|
||||
} else {
|
||||
const popup = Popup.open('disambiguateMultiLabel');
|
||||
// XXX We need to have a better integration between the popup and the
|
||||
// UI components systems.
|
||||
popup.call(Template.currentData(), evt);
|
||||
}
|
||||
},
|
||||
'click .js-toggle-member-multiselection'(evt) {
|
||||
const memberId = Template.currentData()._id;
|
||||
const mappedSelection = mapSelection('member', memberId);
|
||||
if (_.every(mappedSelection)) {
|
||||
mutateSelectedCards('unassignMember', memberId);
|
||||
} else if (_.every(mappedSelection, bool => !bool)) {
|
||||
mutateSelectedCards('assignMember', memberId);
|
||||
} else {
|
||||
const popup = Popup.open('disambiguateMultiMember');
|
||||
// XXX We need to have a better integration between the popup and the
|
||||
// UI components systems.
|
||||
popup.call(Template.currentData(), evt);
|
||||
}
|
||||
},
|
||||
'click .js-move-selection': Popup.open('moveSelection'),
|
||||
'click .js-copy-selection': Popup.open('copySelection'),
|
||||
'click .js-selection-color': Popup.open('setSelectionColor'),
|
||||
'click .js-archive-selection'() {
|
||||
mutateSelectedCards('archive');
|
||||
EscapeActions.executeUpTo('multiselection');
|
||||
},
|
||||
});
|
||||
|
||||
Template.disambiguateMultiLabelPopup.events({
|
||||
|
|
|
|||
|
|
@ -1,41 +1,31 @@
|
|||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.term = new ReactiveVar('');
|
||||
},
|
||||
Template.searchSidebar.onCreated(function () {
|
||||
this.term = new ReactiveVar('');
|
||||
});
|
||||
|
||||
Template.searchSidebar.helpers({
|
||||
cards() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
return currentBoard.searchCards(this.term.get());
|
||||
return currentBoard.searchCards(Template.instance().term.get());
|
||||
},
|
||||
|
||||
lists() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
return currentBoard.searchLists(this.term.get());
|
||||
return currentBoard.searchLists(Template.instance().term.get());
|
||||
},
|
||||
});
|
||||
|
||||
clickOnMiniCard(evt) {
|
||||
Template.searchSidebar.events({
|
||||
'click .js-minicard'(evt) {
|
||||
if (Utils.isMiniScreen()) {
|
||||
evt.preventDefault();
|
||||
Session.set('popupCardId', this.currentData()._id);
|
||||
this.cardDetailsPopup(evt);
|
||||
Session.set('popupCardId', Template.currentData()._id);
|
||||
if (!Popup.isOpen()) {
|
||||
Popup.open("cardDetails")(evt);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
cardDetailsPopup(event) {
|
||||
if (!Popup.isOpen()) {
|
||||
Popup.open("cardDetails")(event);
|
||||
}
|
||||
'submit .js-search-term-form'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
tpl.term.set(evt.target.searchTerm.value);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-minicard': this.clickOnMiniCard,
|
||||
'submit .js-search-term-form'(evt) {
|
||||
evt.preventDefault();
|
||||
this.term.set(evt.target.searchTerm.value);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('searchSidebar');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue