Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc4a0bb462 | ||
|
|
a0418b460a | ||
|
|
0c093b539a | ||
|
|
1971037049 | ||
|
|
57af4ae70d | ||
|
|
8f553497e4 | ||
|
|
0373da44b3 | ||
|
|
1df060b8f6 | ||
|
|
b249fcbb2e | ||
|
|
ff8a36653a | ||
|
|
1c7a9e4de8 | ||
|
|
319783b008 | ||
|
|
0c3b92567e | ||
|
|
3513a3bd62 | ||
|
|
a5f6dd6399 | ||
|
|
7ced6318a5 | ||
|
|
61c691a267 | ||
|
|
52f920db12 | ||
|
|
6d9928ea8f |
No files matched your search
@@ -1,3 +1,25 @@
|
||||
# v4.84 2021-01-22 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Search All Boards: Added list of board, list and color names. Added operators for due, created and modified.
|
||||
Added support for clicking list titles, label names and board titles. Make some heading translatable.
|
||||
Set focus back to search phrase input after clicking a predicate. Fixed some spacing issues](https://github.com/wekan/wekan/pull/3459).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fixed Upper/lowercase errors in some languages due to .toLowerCase](https://github.com/wekan/wekan/commit/a5f6dd6399142b3b05e9b6a0d106d931106807d6).
|
||||
Thanks to bronger and xet7.
|
||||
- [Tried to fix possible prototype pollution reported by Deepcode.ai](https://github.com/wekan/wekan/commit/8f553497e4f21d44e78243d22d80b18f729a3d6a).
|
||||
Thanks to Deepcode.ai and xet7.
|
||||
- [Disable some logs that are not needed anymore](https://github.com/wekan/wekan/commit/0373da44b38f7300f69470fed3cabab9b63c8783).
|
||||
Thanks to xet7.
|
||||
- [Rules not copied during board copy](https://github.com/wekan/wekan/pull/3458).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v4.83 2021-01-20 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v4.83.0"
|
||||
appVersion: "v4.84.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -124,9 +124,13 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
'click .js-clone-board'(evt) {
|
||||
Meteor.call(
|
||||
'cloneBoard',
|
||||
'copyBoard',
|
||||
this.currentData()._id,
|
||||
Session.get('fromBoard'),
|
||||
{
|
||||
sort: Boards.find({ archived: false }).count(),
|
||||
type: 'board',
|
||||
title: Boards.findOne(this.currentData()._id).copyTitle(),
|
||||
},
|
||||
(err, res) => {
|
||||
if (err) {
|
||||
this.setError(err.error);
|
||||
|
||||
@@ -675,12 +675,18 @@ BlazeComponent.extendComponent({
|
||||
element.type = 'swimlane';
|
||||
_id = element.copy(this.boardId);
|
||||
} else if (this.isBoardTemplateSearch) {
|
||||
board = Boards.findOne(element.linkedId);
|
||||
board.sort = Boards.find({ archived: false }).count();
|
||||
board.type = 'board';
|
||||
board.title = element.title;
|
||||
delete board.slug;
|
||||
_id = board.copy();
|
||||
Meteor.call(
|
||||
'copyBoard',
|
||||
element.linkedId,
|
||||
{
|
||||
sort: Boards.find({ archived: false }).count(),
|
||||
type: 'board',
|
||||
title: element.title,
|
||||
},
|
||||
(err, data) => {
|
||||
_id = data;
|
||||
},
|
||||
);
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
|
||||
@@ -14,7 +14,14 @@ template(name="globalSearch")
|
||||
if currentUser
|
||||
.wrapper
|
||||
form.global-search-instructions.js-search-query-form
|
||||
input.global-search-query-input(type="text" name="searchQuery" placeholder="{{_ 'search-example'}}" value="{{ query.get }}" autofocus dir="auto")
|
||||
input.global-search-query-input(
|
||||
id="global-search-input"
|
||||
type="text"
|
||||
name="searchQuery"
|
||||
placeholder="{{_ 'search-example'}}"
|
||||
value="{{ query.get }}"
|
||||
autofocus dir="auto"
|
||||
)
|
||||
if searching.get
|
||||
+spinner
|
||||
else if hasResults.get
|
||||
@@ -32,6 +39,26 @@ template(name="globalSearch")
|
||||
+resultCard(card)
|
||||
else
|
||||
.global-search-instructions
|
||||
h2 {{_ 'boards' }}
|
||||
.lists-wrapper
|
||||
each title in myBoardNames.get
|
||||
span.card-label.list-title.js-board-title
|
||||
= title
|
||||
h2 {{_ 'lists' }}
|
||||
.lists-wrapper
|
||||
each title in myLists.get
|
||||
span.card-label.list-title.js-list-title
|
||||
= title
|
||||
h2 {{_ 'label-colors' }}
|
||||
.palette-colors: each label in labelColors
|
||||
span.card-label.palette-color.js-label-color(class="card-label-{{label.color}}")
|
||||
= label.name
|
||||
if myLabelNames.get.length
|
||||
h2 {{_ 'label-names' }}
|
||||
.lists-wrapper
|
||||
each name in myLabelNames.get
|
||||
span.card-label.list-title.js-label-name
|
||||
= name
|
||||
+viewer
|
||||
= searchInstructions
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ BlazeComponent.extendComponent({
|
||||
this.query = new ReactiveVar('');
|
||||
this.resultsHeading = new ReactiveVar('');
|
||||
this.searchLink = new ReactiveVar(null);
|
||||
this.myLists = new ReactiveVar([]);
|
||||
this.myLabelNames = new ReactiveVar([]);
|
||||
this.myBoardNames = new ReactiveVar([]);
|
||||
this.queryParams = null;
|
||||
this.parsingErrors = [];
|
||||
this.resultsCount = 0;
|
||||
@@ -55,6 +58,25 @@ BlazeComponent.extendComponent({
|
||||
// }
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('colorMap:', this.colorMap);
|
||||
|
||||
Meteor.call('myLists', (err, data) => {
|
||||
if (!err) {
|
||||
this.myLists.set(data);
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.call('myLabelNames', (err, data) => {
|
||||
if (!err) {
|
||||
this.myLabelNames.set(data);
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.call('myBoardNames', (err, data) => {
|
||||
if (!err) {
|
||||
this.myBoardNames.set(data);
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.subscribe('setting');
|
||||
if (Session.get('globalQuery')) {
|
||||
this.searchAllBoards(Session.get('globalQuery'));
|
||||
@@ -111,11 +133,13 @@ BlazeComponent.extendComponent({
|
||||
messages.push({ tag: 'list-title-not-found', value: list });
|
||||
});
|
||||
this.queryErrors.notFound.labels.forEach(label => {
|
||||
const color = TAPi18n.__(`color-${label}`);
|
||||
if (color) {
|
||||
const color = Object.entries(this.colorMap)
|
||||
.filter(value => value[1] === label)
|
||||
.map(value => value[0]);
|
||||
if (color.length) {
|
||||
messages.push({
|
||||
tag: 'label-color-not-found',
|
||||
value: color,
|
||||
value: color[0],
|
||||
});
|
||||
} else {
|
||||
messages.push({ tag: 'label-not-found', value: label });
|
||||
@@ -185,9 +209,12 @@ BlazeComponent.extendComponent({
|
||||
operatorMap[TAPi18n.__('operator-assignee')] = 'assignees';
|
||||
operatorMap[TAPi18n.__('operator-assignee-abbrev')] = 'assignees';
|
||||
operatorMap[TAPi18n.__('operator-is')] = 'is';
|
||||
operatorMap[TAPi18n.__('operator-due')] = 'dueAt';
|
||||
operatorMap[TAPi18n.__('operator-created')] = 'createdAt';
|
||||
operatorMap[TAPi18n.__('operator-modified')] = 'modifiedAt';
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('operatorMap:', operatorMap);
|
||||
console.log('operatorMap:', operatorMap);
|
||||
const params = {
|
||||
boards: [],
|
||||
swimlanes: [],
|
||||
@@ -197,6 +224,9 @@ BlazeComponent.extendComponent({
|
||||
assignees: [],
|
||||
labels: [],
|
||||
is: [],
|
||||
dueAt: null,
|
||||
createdAt: null,
|
||||
modifiedAt: null,
|
||||
};
|
||||
|
||||
let text = '';
|
||||
@@ -217,19 +247,46 @@ BlazeComponent.extendComponent({
|
||||
} else {
|
||||
op = m.groups.abbrev;
|
||||
}
|
||||
if (op in operatorMap) {
|
||||
let value = m.groups.value;
|
||||
if (operatorMap[op] === 'labels') {
|
||||
if (value in this.colorMap) {
|
||||
value = this.colorMap[value];
|
||||
if (op !== "__proto__") {
|
||||
if (op in operatorMap) {
|
||||
let value = m.groups.value;
|
||||
if (operatorMap[op] === 'labels') {
|
||||
if (value in this.colorMap) {
|
||||
value = this.colorMap[value];
|
||||
}
|
||||
} else if (
|
||||
['dueAt', 'createdAt', 'modifiedAt'].includes(operatorMap[op])
|
||||
) {
|
||||
const days = parseInt(value, 10);
|
||||
if (isNaN(days)) {
|
||||
if (['day', 'week', 'month', 'quarter', 'year'].includes(value)) {
|
||||
value = moment()
|
||||
.subtract(1, value)
|
||||
.format();
|
||||
} else {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-number-expected',
|
||||
value: { operator: op, value },
|
||||
});
|
||||
value = null;
|
||||
}
|
||||
} else {
|
||||
value = moment()
|
||||
.subtract(days, 'days')
|
||||
.format();
|
||||
}
|
||||
}
|
||||
if (Array.isArray(params[operatorMap[op]])) {
|
||||
params[operatorMap[op]].push(value);
|
||||
} else {
|
||||
params[operatorMap[op]] = value;
|
||||
}
|
||||
} else {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-unknown-error',
|
||||
value: op,
|
||||
});
|
||||
}
|
||||
params[operatorMap[op]].push(value);
|
||||
} else {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-unknown-error',
|
||||
value: op,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -355,6 +412,14 @@ BlazeComponent.extendComponent({
|
||||
return text;
|
||||
},
|
||||
|
||||
labelColors() {
|
||||
return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map(
|
||||
color => {
|
||||
return { color, name: TAPi18n.__(`color-${color}`) };
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
@@ -362,6 +427,42 @@ BlazeComponent.extendComponent({
|
||||
evt.preventDefault();
|
||||
this.searchAllBoards(evt.target.searchQuery.value);
|
||||
},
|
||||
'click .js-label-color'(evt) {
|
||||
evt.preventDefault();
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-label')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
document.getElementById('global-search-input').focus();
|
||||
},
|
||||
'click .js-board-title'(evt) {
|
||||
evt.preventDefault();
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-board')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
document.getElementById('global-search-input').focus();
|
||||
},
|
||||
'click .js-list-title'(evt) {
|
||||
evt.preventDefault();
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-list')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
document.getElementById('global-search-input').focus();
|
||||
},
|
||||
'click .js-label-name'(evt) {
|
||||
evt.preventDefault();
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-label')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
document.getElementById('global-search-input').focus();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -78,6 +78,12 @@
|
||||
margin-left: auto
|
||||
line-height: 150%
|
||||
|
||||
.global-search-instructions h1
|
||||
margin-top: 2rem;
|
||||
|
||||
.global-search-instructions h2
|
||||
margin-top: 1rem;
|
||||
|
||||
.global-search-query-input
|
||||
width: 90% !important
|
||||
margin-right: auto
|
||||
@@ -95,3 +101,6 @@ code
|
||||
background-color: lightgrey
|
||||
padding: 0.1rem !important
|
||||
font-size: 0.7rem !important
|
||||
|
||||
.list-title
|
||||
background-color: darkgray
|
||||
@@ -166,16 +166,16 @@ Template.memberPopup.helpers({
|
||||
const noComments = currentBoard.hasNoComments(this.userId);
|
||||
const worker = currentBoard.hasWorker(this.userId);
|
||||
if (commentOnly) {
|
||||
return TAPi18n.__('comment-only').toLowerCase();
|
||||
return TAPi18n.__('comment-only');
|
||||
} else if (noComments) {
|
||||
return TAPi18n.__('no-comments').toLowerCase();
|
||||
return TAPi18n.__('no-comments');
|
||||
} else if (worker) {
|
||||
return TAPi18n.__('worker').toLowerCase();
|
||||
return TAPi18n.__('worker');
|
||||
} else {
|
||||
return TAPi18n.__(type).toLowerCase();
|
||||
return TAPi18n.__(type);
|
||||
}
|
||||
} else {
|
||||
return TAPi18n.__(type).toLowerCase();
|
||||
return TAPi18n.__(type);
|
||||
}
|
||||
},
|
||||
isInvited() {
|
||||
|
||||
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+7
-1
@@ -895,7 +895,11 @@
|
||||
"operator-assignee": "assignee",
|
||||
"operator-assignee-abbrev": "a",
|
||||
"operator-is": "is",
|
||||
"operator-due": "due",
|
||||
"operator-created": "created",
|
||||
"operator-modified": "modified",
|
||||
"operator-unknown-error": "%s is not an operator",
|
||||
"operator-number-expected": "operator __operator__ expected a number, got '__value__'",
|
||||
"heading-notes": "Notes",
|
||||
"globalSearch-instructions-heading": "Search Instructions",
|
||||
"globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).",
|
||||
@@ -916,5 +920,7 @@
|
||||
"globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
|
||||
"link-to-search": "Link to this search",
|
||||
"excel-font": "Arial",
|
||||
"number": "Number"
|
||||
"number": "Number",
|
||||
"label-colors": "Label Colors",
|
||||
"label-names": "Label Names"
|
||||
}
|
||||
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
+924
-918
File diff suppressed because it is too large.
Load diff
@@ -508,6 +508,7 @@ Boards.helpers({
|
||||
copy() {
|
||||
const oldId = this._id;
|
||||
delete this._id;
|
||||
delete this.slug;
|
||||
const _id = Boards.insert(this);
|
||||
|
||||
// Copy all swimlanes in board
|
||||
@@ -537,7 +538,52 @@ Boards.helpers({
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// copy rules, actions, and triggers
|
||||
const actionsMap = {};
|
||||
Actions.find({ boardId: oldId }).forEach(action => {
|
||||
const id = action._id;
|
||||
delete action._id;
|
||||
action.boardId = _id;
|
||||
actionsMap[id] = Actions.insert(action);
|
||||
});
|
||||
const triggersMap = {};
|
||||
Triggers.find({ boardId: oldId }).forEach(trigger => {
|
||||
const id = trigger._id;
|
||||
delete trigger._id;
|
||||
trigger.boardId = _id;
|
||||
triggersMap[id] = Triggers.insert(trigger);
|
||||
});
|
||||
Rules.find({ boardId: oldId }).forEach(rule => {
|
||||
delete rule._id;
|
||||
rule.boardId = _id;
|
||||
rule.actionId = actionsMap[rule.actionId];
|
||||
rule.triggerId = triggersMap[rule.triggerId];
|
||||
Rules.insert(rule);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Return a unique title based on the current title
|
||||
*
|
||||
* @returns {string|null}
|
||||
*/
|
||||
copyTitle() {
|
||||
const m = this.title.match(/^(?<title>.*?)\s*(\[(?<num>\d+)]\s*$|\s*$)/);
|
||||
const title = m.groups.title;
|
||||
let num = 0;
|
||||
Boards.find({ title: new RegExp(`^${title}\\s*\\[\\d+]\\s*$`) }).forEach(
|
||||
board => {
|
||||
const m = board.title.match(/^(?<title>.*?)\s*\[(?<num>\d+)]\s*$/);
|
||||
if (m) {
|
||||
const n = parseInt(m.groups.num, 10);
|
||||
num = num < n ? n : num;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return `${title} [${num + 1}]`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Is supplied user authorized to view this board?
|
||||
*/
|
||||
@@ -1343,6 +1389,26 @@ if (Meteor.isServer) {
|
||||
},
|
||||
});
|
||||
},
|
||||
myLabelNames() {
|
||||
let names = [];
|
||||
Boards.userBoards(Meteor.userId()).forEach(board => {
|
||||
names = names.concat(
|
||||
board.labels
|
||||
.filter(label => !!label.name)
|
||||
.map(label => {
|
||||
return label.name;
|
||||
}),
|
||||
);
|
||||
});
|
||||
return _.uniq(names).sort();
|
||||
},
|
||||
myBoardNames() {
|
||||
return _.uniq(
|
||||
Boards.userBoards(Meteor.userId()).map(board => {
|
||||
return board.title;
|
||||
}),
|
||||
).sort();
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
|
||||
+15
-1
@@ -1954,6 +1954,18 @@ Cards.globalSearch = queryParams => {
|
||||
selector.listId.$in = queryLists;
|
||||
}
|
||||
|
||||
if (queryParams.dueAt !== null) {
|
||||
selector.dueAt = { $gte: new Date(queryParams.dueAt) };
|
||||
}
|
||||
|
||||
if (queryParams.createdAt !== null) {
|
||||
selector.createdAt = { $gte: new Date(queryParams.createdAt) };
|
||||
}
|
||||
|
||||
if (queryParams.modifiedAt !== null) {
|
||||
selector.modifiedAt = { $gte: new Date(queryParams.modifiedAt) };
|
||||
}
|
||||
|
||||
const queryMembers = [];
|
||||
const queryAssignees = [];
|
||||
if (queryParams.users.length) {
|
||||
@@ -2094,13 +2106,15 @@ Cards.globalSearch = queryParams => {
|
||||
assignees: 1,
|
||||
colors: 1,
|
||||
dueAt: 1,
|
||||
createdAt: 1,
|
||||
modifiedAt: 1,
|
||||
labelIds: 1,
|
||||
},
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('count:', cards.count());
|
||||
//console.log('count:', cards.count());
|
||||
|
||||
return { cards, errors };
|
||||
};
|
||||
|
||||
@@ -362,6 +362,20 @@ Meteor.methods({
|
||||
const list = Lists.findOne({ _id: listId });
|
||||
list.toggleSoftLimit(!list.getWipLimit('soft'));
|
||||
},
|
||||
|
||||
myLists() {
|
||||
// my lists
|
||||
return _.uniq(
|
||||
Lists.find(
|
||||
{ boardId: { $in: Boards.userBoardIds(this.userId) } },
|
||||
{ fields: { title: 1 } },
|
||||
)
|
||||
.fetch()
|
||||
.map(list => {
|
||||
return list.title;
|
||||
}),
|
||||
).sort();
|
||||
},
|
||||
});
|
||||
|
||||
Lists.hookOptions.after.update = { fetchPrevious: false };
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wekan",
|
||||
"version": "v4.83.0",
|
||||
"version": "v4.84.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wekan",
|
||||
"version": "v4.83.0",
|
||||
"version": "v4.84.0",
|
||||
"description": "Open-Source kanban",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
+183
-2696
File diff suppressed because it is too large.
Load diff
+1
-624
@@ -1,7 +1,7 @@
|
||||
swagger: '2.0'
|
||||
info:
|
||||
title: Wekan REST API
|
||||
version: v4.83
|
||||
version: v4.84
|
||||
description: |
|
||||
The REST API allows you to control and extend Wekan with ease.
|
||||
|
||||
@@ -134,198 +134,6 @@ paths:
|
||||
description: |
|
||||
Error in registration
|
||||
|
||||
/api/boards:
|
||||
get:
|
||||
operationId: get_public_boards
|
||||
summary: Get all public boards
|
||||
tags:
|
||||
- Boards
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
_id:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
post:
|
||||
operationId: new_board
|
||||
summary: Create a board
|
||||
description: |
|
||||
This allows to create a board.
|
||||
|
||||
The color has to be chosen between `belize`, `nephritis`, `pomegranate`,
|
||||
`pumpkin`, `wisteria`, `moderatepink`, `strongcyan`,
|
||||
`limegreen`, `midnight`, `dark`, `relax`, `corteza`:
|
||||
|
||||
<img src="https://wekan.github.io/board-colors.png" width="40%" alt="Wekan logo" />
|
||||
tags:
|
||||
- Boards
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
- application/json
|
||||
parameters:
|
||||
- name: title
|
||||
in: formData
|
||||
description: |
|
||||
the new title of the board
|
||||
type: string
|
||||
required: true
|
||||
- name: owner
|
||||
in: formData
|
||||
description: |
|
||||
"ABCDE12345" <= User ID in Wekan.
|
||||
(Not username or email)
|
||||
type: string
|
||||
required: true
|
||||
- name: isAdmin
|
||||
in: formData
|
||||
description: |
|
||||
is the owner an admin of the board (default true)
|
||||
type: boolean
|
||||
required: false
|
||||
- name: isActive
|
||||
in: formData
|
||||
description: |
|
||||
is the board active (default true)
|
||||
type: boolean
|
||||
required: false
|
||||
- name: isNoComments
|
||||
in: formData
|
||||
description: |
|
||||
disable comments (default false)
|
||||
type: boolean
|
||||
required: false
|
||||
- name: isCommentOnly
|
||||
in: formData
|
||||
description: |
|
||||
only enable comments (default false)
|
||||
type: boolean
|
||||
required: false
|
||||
- name: isWorker
|
||||
in: formData
|
||||
description: |
|
||||
only move cards, assign himself to card and comment (default false)
|
||||
type: boolean
|
||||
required: false
|
||||
- name: permission
|
||||
in: formData
|
||||
description: |
|
||||
"private" board <== Set to "public" if you
|
||||
want public Wekan board
|
||||
type: string
|
||||
required: false
|
||||
- name: color
|
||||
in: formData
|
||||
description: |
|
||||
the color of the board
|
||||
type: string
|
||||
required: false
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
_id:
|
||||
type: string
|
||||
defaultSwimlaneId:
|
||||
type: string
|
||||
/api/boards/{board}:
|
||||
get:
|
||||
operationId: get_board
|
||||
summary: Get the board with that particular ID
|
||||
tags:
|
||||
- Boards
|
||||
parameters:
|
||||
- name: board
|
||||
in: path
|
||||
description: |
|
||||
the ID of the board to retrieve the data
|
||||
type: string
|
||||
required: true
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
schema:
|
||||
$ref: "#/definitions/Boards"
|
||||
delete:
|
||||
operationId: delete_board
|
||||
summary: Delete a board
|
||||
tags:
|
||||
- Boards
|
||||
parameters:
|
||||
- name: board
|
||||
in: path
|
||||
description: |
|
||||
the ID of the board
|
||||
type: string
|
||||
required: true
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
/api/boards/{board}/attachments:
|
||||
get:
|
||||
operationId: get_board_attachments
|
||||
summary: Get the list of attachments of a board
|
||||
tags:
|
||||
- Boards
|
||||
parameters:
|
||||
- name: board
|
||||
in: path
|
||||
description: |
|
||||
the board ID
|
||||
type: string
|
||||
required: true
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
attachmentId:
|
||||
type: string
|
||||
attachmentName:
|
||||
type: string
|
||||
attachmentType:
|
||||
type: string
|
||||
cardId:
|
||||
type: string
|
||||
listId:
|
||||
type: string
|
||||
swimlaneId:
|
||||
type: string
|
||||
/api/boards/{board}/cards/{card}/checklists:
|
||||
get:
|
||||
operationId: get_all_checklists
|
||||
@@ -1288,40 +1096,6 @@ paths:
|
||||
200 response
|
||||
schema:
|
||||
$ref: "#/definitions/Integrations"
|
||||
/api/boards/{board}/labels:
|
||||
put:
|
||||
operationId: add_board_label
|
||||
summary: Add a label to a board
|
||||
description: |
|
||||
If the board doesn't have the name/color label, this function
|
||||
adds the label to the board.
|
||||
tags:
|
||||
- Boards
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
- application/json
|
||||
parameters:
|
||||
- name: label
|
||||
in: formData
|
||||
description: the label value
|
||||
type: string
|
||||
required: true
|
||||
- name: board
|
||||
in: path
|
||||
description: |
|
||||
the board
|
||||
type: string
|
||||
required: true
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
schema:
|
||||
type: string
|
||||
/api/boards/{board}/lists:
|
||||
get:
|
||||
operationId: get_all_lists
|
||||
@@ -1803,61 +1577,6 @@ paths:
|
||||
properties:
|
||||
_id:
|
||||
type: string
|
||||
/api/boards/{board}/members/{member}:
|
||||
post:
|
||||
operationId: set_board_member_permission
|
||||
summary: Change the permission of a member of a board
|
||||
tags:
|
||||
- Boards
|
||||
- Users
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
- application/json
|
||||
parameters:
|
||||
- name: isAdmin
|
||||
in: formData
|
||||
description: |
|
||||
admin capability
|
||||
type: boolean
|
||||
required: true
|
||||
- name: isNoComments
|
||||
in: formData
|
||||
description: |
|
||||
NoComments capability
|
||||
type: boolean
|
||||
required: true
|
||||
- name: isCommentOnly
|
||||
in: formData
|
||||
description: |
|
||||
CommentsOnly capability
|
||||
type: boolean
|
||||
required: true
|
||||
- name: isWorker
|
||||
in: formData
|
||||
description: |
|
||||
Worker capability
|
||||
type: boolean
|
||||
required: true
|
||||
- name: board
|
||||
in: path
|
||||
description: |
|
||||
the ID of the board that we are changing
|
||||
type: string
|
||||
required: true
|
||||
- name: member
|
||||
in: path
|
||||
description: |
|
||||
the ID of the user to change permissions
|
||||
type: string
|
||||
required: true
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
/api/boards/{board}/members/{user}/add:
|
||||
post:
|
||||
operationId: add_board_member
|
||||
@@ -2314,349 +2033,7 @@ paths:
|
||||
properties:
|
||||
_id:
|
||||
type: string
|
||||
/api/users/{user}/boards:
|
||||
get:
|
||||
operationId: get_boards_from_user
|
||||
summary: Get all boards attached to a user
|
||||
tags:
|
||||
- Boards
|
||||
parameters:
|
||||
- name: user
|
||||
in: path
|
||||
description: |
|
||||
the ID of the user to retrieve the data
|
||||
type: string
|
||||
required: true
|
||||
produces:
|
||||
- application/json
|
||||
security:
|
||||
- UserSecurity: []
|
||||
responses:
|
||||
'200':
|
||||
description: |-
|
||||
200 response
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
_id:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
definitions:
|
||||
Boards:
|
||||
type: object
|
||||
description: This is a Board.
|
||||
properties:
|
||||
title:
|
||||
description: |
|
||||
The title of the board
|
||||
type: string
|
||||
slug:
|
||||
description: |
|
||||
The title slugified.
|
||||
type: string
|
||||
archived:
|
||||
description: |
|
||||
Is the board archived?
|
||||
type: boolean
|
||||
createdAt:
|
||||
description: |
|
||||
Creation time of the board
|
||||
type: string
|
||||
modifiedAt:
|
||||
description: |
|
||||
Last modification time of the board
|
||||
type: string
|
||||
x-nullable: true
|
||||
stars:
|
||||
description: |
|
||||
How many stars the board has
|
||||
type: number
|
||||
labels:
|
||||
description: |
|
||||
List of labels attached to a board
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/BoardsLabels"
|
||||
members:
|
||||
description: |
|
||||
List of members of a board
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/BoardsMembers"
|
||||
permission:
|
||||
description: |
|
||||
visibility of the board
|
||||
type: string
|
||||
enum:
|
||||
- public
|
||||
- private
|
||||
color:
|
||||
description: |
|
||||
The color of the board.
|
||||
type: string
|
||||
enum:
|
||||
- belize
|
||||
- nephritis
|
||||
- pomegranate
|
||||
- pumpkin
|
||||
- wisteria
|
||||
- moderatepink
|
||||
- strongcyan
|
||||
- limegreen
|
||||
- midnight
|
||||
- dark
|
||||
- relax
|
||||
- corteza
|
||||
- clearblue
|
||||
- natural
|
||||
- modern
|
||||
- moderndark
|
||||
description:
|
||||
description: |
|
||||
The description of the board
|
||||
type: string
|
||||
x-nullable: true
|
||||
subtasksDefaultBoardId:
|
||||
description: |
|
||||
The default board ID assigned to subtasks.
|
||||
type: string
|
||||
x-nullable: true
|
||||
subtasksDefaultListId:
|
||||
description: |
|
||||
The default List ID assigned to subtasks.
|
||||
type: string
|
||||
x-nullable: true
|
||||
dateSettingsDefaultBoardId:
|
||||
type: string
|
||||
x-nullable: true
|
||||
dateSettingsDefaultListId:
|
||||
type: string
|
||||
x-nullable: true
|
||||
allowsSubtasks:
|
||||
description: |
|
||||
Does the board allows subtasks?
|
||||
type: boolean
|
||||
allowsAttachments:
|
||||
description: |
|
||||
Does the board allows attachments?
|
||||
type: boolean
|
||||
allowsChecklists:
|
||||
description: |
|
||||
Does the board allows checklists?
|
||||
type: boolean
|
||||
allowsComments:
|
||||
description: |
|
||||
Does the board allows comments?
|
||||
type: boolean
|
||||
allowsDescriptionTitle:
|
||||
description: |
|
||||
Does the board allows description title?
|
||||
type: boolean
|
||||
allowsDescriptionText:
|
||||
description: |
|
||||
Does the board allows description text?
|
||||
type: boolean
|
||||
allowsActivities:
|
||||
description: |
|
||||
Does the board allows comments?
|
||||
type: boolean
|
||||
allowsLabels:
|
||||
description: |
|
||||
Does the board allows labels?
|
||||
type: boolean
|
||||
allowsAssignee:
|
||||
description: |
|
||||
Does the board allows assignee?
|
||||
type: boolean
|
||||
allowsMembers:
|
||||
description: |
|
||||
Does the board allows members?
|
||||
type: boolean
|
||||
allowsRequestedBy:
|
||||
description: |
|
||||
Does the board allows requested by?
|
||||
type: boolean
|
||||
allowsAssignedBy:
|
||||
description: |
|
||||
Does the board allows requested by?
|
||||
type: boolean
|
||||
allowsReceivedDate:
|
||||
description: |
|
||||
Does the board allows received date?
|
||||
type: boolean
|
||||
allowsStartDate:
|
||||
description: |
|
||||
Does the board allows start date?
|
||||
type: boolean
|
||||
allowsEndDate:
|
||||
description: |
|
||||
Does the board allows end date?
|
||||
type: boolean
|
||||
allowsDueDate:
|
||||
description: |
|
||||
Does the board allows due date?
|
||||
type: boolean
|
||||
presentParentTask:
|
||||
description: |
|
||||
Controls how to present the parent task:
|
||||
|
||||
- `prefix-with-full-path`: add a prefix with the full path
|
||||
- `prefix-with-parent`: add a prefisx with the parent name
|
||||
- `subtext-with-full-path`: add a subtext with the full path
|
||||
- `subtext-with-parent`: add a subtext with the parent name
|
||||
- `no-parent`: does not show the parent at all
|
||||
type: string
|
||||
enum:
|
||||
- prefix-with-full-path
|
||||
- prefix-with-parent
|
||||
- subtext-with-full-path
|
||||
- subtext-with-parent
|
||||
- no-parent
|
||||
x-nullable: true
|
||||
startAt:
|
||||
description: |
|
||||
Starting date of the board.
|
||||
type: string
|
||||
x-nullable: true
|
||||
dueAt:
|
||||
description: |
|
||||
Due date of the board.
|
||||
type: string
|
||||
x-nullable: true
|
||||
endAt:
|
||||
description: |
|
||||
End date of the board.
|
||||
type: string
|
||||
x-nullable: true
|
||||
spentTime:
|
||||
description: |
|
||||
Time spent in the board.
|
||||
type: number
|
||||
x-nullable: true
|
||||
isOvertime:
|
||||
description: |
|
||||
Is the board overtimed?
|
||||
type: boolean
|
||||
x-nullable: true
|
||||
type:
|
||||
description: |
|
||||
The type of board
|
||||
type: string
|
||||
sort:
|
||||
description: |
|
||||
Sort value
|
||||
type: number
|
||||
required:
|
||||
- title
|
||||
- slug
|
||||
- archived
|
||||
- createdAt
|
||||
- stars
|
||||
- labels
|
||||
- members
|
||||
- permission
|
||||
- color
|
||||
- allowsSubtasks
|
||||
- allowsAttachments
|
||||
- allowsChecklists
|
||||
- allowsComments
|
||||
- allowsDescriptionTitle
|
||||
- allowsDescriptionText
|
||||
- allowsActivities
|
||||
- allowsLabels
|
||||
- allowsAssignee
|
||||
- allowsMembers
|
||||
- allowsRequestedBy
|
||||
- allowsAssignedBy
|
||||
- allowsReceivedDate
|
||||
- allowsStartDate
|
||||
- allowsEndDate
|
||||
- allowsDueDate
|
||||
- type
|
||||
- sort
|
||||
BoardsLabels:
|
||||
type: object
|
||||
properties:
|
||||
_id:
|
||||
description: |
|
||||
Unique id of a label
|
||||
type: string
|
||||
name:
|
||||
description: |
|
||||
Name of a label
|
||||
type: string
|
||||
color:
|
||||
description: |
|
||||
color of a label.
|
||||
|
||||
Can be amongst `green`, `yellow`, `orange`, `red`, `purple`,
|
||||
`blue`, `sky`, `lime`, `pink`, `black`,
|
||||
`silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`,
|
||||
`slateblue`, `magenta`, `gold`, `navy`, `gray`,
|
||||
`saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`
|
||||
type: string
|
||||
enum:
|
||||
- green
|
||||
- yellow
|
||||
- orange
|
||||
- red
|
||||
- purple
|
||||
- blue
|
||||
- sky
|
||||
- lime
|
||||
- pink
|
||||
- black
|
||||
- silver
|
||||
- peachpuff
|
||||
- crimson
|
||||
- plum
|
||||
- darkgreen
|
||||
- slateblue
|
||||
- magenta
|
||||
- gold
|
||||
- navy
|
||||
- gray
|
||||
- saddlebrown
|
||||
- paleturquoise
|
||||
- mistyrose
|
||||
- indigo
|
||||
required:
|
||||
- _id
|
||||
- color
|
||||
BoardsMembers:
|
||||
type: object
|
||||
properties:
|
||||
userId:
|
||||
description: |
|
||||
The uniq ID of the member
|
||||
type: string
|
||||
isAdmin:
|
||||
description: |
|
||||
Is the member an admin of the board?
|
||||
type: boolean
|
||||
isActive:
|
||||
description: |
|
||||
Is the member active?
|
||||
type: boolean
|
||||
isNoComments:
|
||||
description: |
|
||||
Is the member not allowed to make comments
|
||||
type: boolean
|
||||
isCommentOnly:
|
||||
description: |
|
||||
Is the member only allowed to comment on the board
|
||||
type: boolean
|
||||
isWorker:
|
||||
description: |
|
||||
Is the member only allowed to move card, assign himself to card and comment
|
||||
type: boolean
|
||||
required:
|
||||
- userId
|
||||
- isAdmin
|
||||
- isActive
|
||||
CardComments:
|
||||
type: object
|
||||
description: A comment on a card
|
||||
|
||||
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
|
||||
appTitle = (defaultText = "Wekan"),
|
||||
# The name of the app as it is displayed to the user.
|
||||
|
||||
appVersion = 483,
|
||||
appVersion = 484,
|
||||
# Increment this for every release.
|
||||
|
||||
appMarketingVersion = (defaultText = "4.83.0~2021-01-20"),
|
||||
appMarketingVersion = (defaultText = "4.84.0~2021-01-22"),
|
||||
# Human-readable presentation of the app version.
|
||||
|
||||
minUpgradableAppVersion = 0,
|
||||
|
||||
@@ -209,3 +209,19 @@ Meteor.publishRelations('board', function(boardId, isArchived) {
|
||||
|
||||
return this.ready();
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
copyBoard(boardId, properties) {
|
||||
check(boardId, String);
|
||||
check(properties, Object);
|
||||
|
||||
const board = Boards.findOne(boardId);
|
||||
if (board) {
|
||||
for (const key in properties) {
|
||||
board[key] = properties[key];
|
||||
}
|
||||
return board.copy();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user