Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5adaaa7f22 | ||
|
|
5b77543ab5 | ||
|
|
0ff215f78f | ||
|
|
ff787bb6ca | ||
|
|
334dfa5cb6 | ||
|
|
a17e628872 | ||
|
|
e87ce4cac5 | ||
|
|
8678e77c65 | ||
|
|
66f99f9ad7 | ||
|
|
b1cdcda8ed | ||
|
|
4c6084ab68 | ||
|
|
fa51ce6fb0 | ||
|
|
5708954268 | ||
|
|
5f23e9caa7 | ||
|
|
37372466cc | ||
|
|
442a7b0fcd | ||
|
|
e0576502d9 | ||
|
|
f8e2fe5794 | ||
|
|
c10f32cc30 | ||
|
|
feb477ca48 | ||
|
|
2d1e4e8a1d | ||
|
|
ef166468e9 | ||
|
|
e58de99d0f | ||
|
|
56c7b7a8f1 | ||
|
|
2df0a54cb8 | ||
|
|
78a1d08a17 | ||
|
|
4effccf96e | ||
|
|
282505cde2 | ||
|
|
d2d4c19196 | ||
|
|
e6d42f21a2 | ||
|
|
71483e8b04 | ||
|
|
54a55a4a23 | ||
|
|
bad4cc12b4 | ||
|
|
6289acd109 | ||
|
|
4e8fc46475 | ||
|
|
4bd3329d07 | ||
|
|
4163841bcf | ||
|
|
7ad1171c5f | ||
|
|
6a32424a08 | ||
|
|
2c30714c6f | ||
|
|
aa22d4e569 | ||
|
|
ac020a401f | ||
|
|
be238ac743 | ||
|
|
f377e66f02 | ||
|
|
6ea79dff51 | ||
|
|
ba336de74b | ||
|
|
b6b3682d06 | ||
|
|
927d15f2d8 | ||
|
|
31f89121fe | ||
|
|
1189b66748 | ||
|
|
a9ceba690e | ||
|
|
f6370782ec | ||
|
|
4c0bd359f9 | ||
|
|
76491515d9 | ||
|
|
2977f7cf47 | ||
|
|
931bff608c | ||
|
|
fea445c902 | ||
|
|
027771b302 | ||
|
|
f2e7296d19 | ||
|
|
20ab2f039b | ||
|
|
158a0807d9 | ||
|
|
814ae5e840 | ||
|
|
37d76e9e06 | ||
|
|
6ba2bb5087 | ||
|
|
4faab518a7 | ||
|
|
e0c6f64788 | ||
|
|
211d27352a | ||
|
|
74ad7b9f8d | ||
|
|
c66a48e530 | ||
|
|
91ef8ca1ae | ||
|
|
9b6288e49c | ||
|
|
9eca4566bf | ||
|
|
907bf4ffdc | ||
|
|
5b194285f5 | ||
|
|
ae1269aa68 | ||
|
|
d1c3c85af8 | ||
|
|
143f259904 | ||
|
|
082b16d422 | ||
|
|
dd163b9923 | ||
|
|
7a5aad9cd8 | ||
|
|
9b813dd6a1 | ||
|
|
66f5f254d0 | ||
|
|
b40d53df84 | ||
|
|
0c54260762 | ||
|
|
5346f89f77 | ||
|
|
7909af6a56 | ||
|
|
ef37cb762d | ||
|
|
3799839539 | ||
|
|
81d496bac7 | ||
|
|
528c0d212a | ||
|
|
3b876b2d20 | ||
|
|
a7bd8d42e8 | ||
|
|
f8f14613f9 | ||
|
|
c9db8e0a26 | ||
|
|
8dd088d727 | ||
|
|
550f7201bb | ||
|
|
e1c14b04ad | ||
|
|
fb690e47e8 | ||
|
|
b537275707 | ||
|
|
545c72fca3 | ||
|
|
39413faff0 | ||
|
|
953cfd6ecd | ||
|
|
36919a25be | ||
|
|
7a66cb46a0 | ||
|
|
3a2926c22e | ||
|
|
591a8cd807 | ||
|
|
4a4c8c2404 | ||
|
|
9d2bb505ae | ||
|
|
2531cf549e | ||
|
|
80c395906b | ||
|
|
da12c84609 | ||
|
|
69c0a3cba3 | ||
|
|
124bce8f80 | ||
|
|
1d37f170fd | ||
|
|
99bdee5b12 | ||
|
|
5476a5984c | ||
|
|
74d93a6adc | ||
|
|
fb9524bba8 | ||
|
|
f56caa745a | ||
|
|
fc0ee2e41b |
No files matched your search
+117
@@ -1,3 +1,120 @@
|
||||
# v4.89 2021-01-28 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix quotes in Global Search](https://github.com/wekan/wekan/commit/0ff215f78f03c81d153dfc0ffa08fac94b542ec2).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v4.88 2021-01-28 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Additional URL schemes: SolidWorks PDM (conisio:) and abas ERP (abasurl:)](https://github.com/wekan/wekan/pull/3487).
|
||||
Thanks to tod31.
|
||||
|
||||
and adds the following improvements:
|
||||
|
||||
- [Mobile and Desktop have now the same Quick Access view + scrollable](https://github.com/wekan/wekan/pull/3491).
|
||||
Thanks to mfilser.
|
||||
- [Global Search Update](https://github.com/wekan/wekan/pull/3492).
|
||||
Thanks to jrsupplee.
|
||||
- [Added many more fields to Export to Excel, and better formatting. Does not yet have all
|
||||
fields](https://github.com/wekan/wekan/commit/37372466ccd15c7d5d4a55510b349fac0953c425).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Changed method to create initials same as others for new user of oidc](https://github.com/wekan/wekan/pull/3489).
|
||||
Thanks to sato-64bit.
|
||||
- [Removed quotes from docker-compose.yml settings](https://github.com/wekan/wekan/commit/b1cdcda8ed78d48505a8da5180d7aed46a24fd64).
|
||||
Thanks to XL-Reaper, Vinc89 and xet7.
|
||||
- [Repair LDAP_REJECT_UNAUTHORIZED=false CVE-2021-3309](https://github.com/wekan/wekan/pull/3497).
|
||||
Thanks to robert-scheck.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v4.87 2021-01-26 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Reject by default LDAP connections not authorized via CA trust store](https://github.com/wekan/wekan/pull/3483).
|
||||
Thanks to robert-scheck.
|
||||
- [Handle '\n' line breaks in PEM-encoded SSL/TLS certificates](https://github.com/wekan/wekan/pull/3485).
|
||||
Thanks to robert-scheck.
|
||||
|
||||
and adds the following improvements
|
||||
|
||||
- [Try parallel build of releases, does it work](https://github.com/wekan/wekan/commit/be238ac7439ce38b4403d9a611dec9bb421a856f).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v4.86 2021-01-25 Wekan release
|
||||
|
||||
This release adds the following improvements:
|
||||
|
||||
- [Added PWA related category, orientation, screenshots, maskable icon and
|
||||
IARC rating ID](https://github.com/wekan/wekan/commit/027771b3021a709d9049015e7d7e6faccf1ad7f3).
|
||||
Thanks to xet7.
|
||||
- [Added PWA related monochrome icon](https://github.com/wekan/wekan/commit/2977f7cf47626b429159cb7b7496919c07ece914).
|
||||
Thanks to xet7.
|
||||
- [Mention Wekan GitHub Discussions at readme](https://github.com/wekan/wekan/commit/4c0bd359f921ae0ea722f78946fcc1168e8b939e).
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Use Node 12.20.1 in rebuild-wekan.sh](https://github.com/wekan/wekan/commit/37d76e9e061d31c11fca8e704e9b4c54f17c0023).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Move call to URL search to onRendered](https://github.com/wekan/wekan/pull/3478).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v4.85 2021-01-23 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Option to add custom field to all cards](https://github.com/wekan/wekan/pulls/3466).
|
||||
Thanks to jrsupplee.
|
||||
- [Added checkbox as an option to custom field create dialog](https://github.com/wekan/wekan/pull/3472).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
and adds the following improvements:
|
||||
|
||||
- [Display My Cards lists using inline-grid](https://github.com/wekan/wekan/pull/3474).
|
||||
Thanks to jrsupplee.
|
||||
- [Added board title link and background color to My Cards](https://github.com/wekan/wekan/pull/3471).
|
||||
Thanks to helioguardabaxo.
|
||||
- [Use simple border at My Cards](https://github.com/wekan/wekan/pull/3475).
|
||||
Thanks to helioguardabaxo.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- Updated dependencies.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/7a66cb46a0ec334f4e95a73322641ba029f770ad),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/953cfd6ecd291196ce2ad1d4a5eac19ca21a20d9).
|
||||
Thanks to developers of dependencies.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [WIP Limit: Limited number of cards highlighting to true overbooking](https://github.com/wekan/wekan/pull/3468).
|
||||
Thanks to bronger.
|
||||
- [Revert table-cell back to inline-block at my-cards-list-wrapper](https://github.com/wekan/wekan/commit/da12c84609674bdf5121ad6b74c97c65b9fc0164).
|
||||
Thanks to jrsupplee and xet7.
|
||||
- [Fix for search operators with uppercase letters](https://github.com/wekan/wekan/pull/3470).
|
||||
Thanks to jrsupplee.
|
||||
- [Boards.copyTitle - escape string used in regex](https://github.com/wekan/wekan/pull/3473).
|
||||
Thanks to jrsupplee.
|
||||
- [Bug fix: import regex escape function](https://github.com/wekan/wekan/pull/3476).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v4.84 2021-01-22 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
@@ -20,13 +20,12 @@ New English strings of new features can be added as PRs to edge branch file weka
|
||||
|
||||
## [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues)
|
||||
|
||||
Please add most of your questions as GitHub issue: [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues).
|
||||
Please add most of your questions as GitHub issue: [Wekan Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
|
||||
It's better than at chat where details get lost when chat scrolls up.
|
||||
|
||||
## Chat
|
||||
|
||||
[![Wekan Chat][vanila_badge]][wekan_chat] - Most Wekan community and developers are here. Works on webbrowser
|
||||
and PWA app that can be added as icon on Android and bookmark on iOS, used like native app.
|
||||
[Discussions][discussions] - Wekan Community GitHub Discussions, that are not [Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
|
||||
|
||||
[Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
|
||||
|
||||
@@ -114,8 +113,6 @@ with [Meteor](https://www.meteor.com).
|
||||
[translate_wekan]: https://www.transifex.com/wekan/wekan/
|
||||
[open_source]: https://en.wikipedia.org/wiki/Open-source_software
|
||||
[free_software]: https://en.wikipedia.org/wiki/Free_software
|
||||
[vanila_badge]: https://vanila.io/img/join-chat-button2.png
|
||||
[wekan_chat]: https://community.vanila.io/wekan
|
||||
|
||||
[discussions]: https://github.com/wekan/wekan/discussions
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_large)
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v4.84.0"
|
||||
appVersion: "v4.89.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -5,19 +5,28 @@ template(name="resultCard")
|
||||
//= card.title
|
||||
ul.result-card-context-list
|
||||
li.result-card-context(title="{{_ 'board'}}")
|
||||
+viewer
|
||||
= getBoard.title
|
||||
.result-card-block-wrapper
|
||||
+viewer
|
||||
= getBoard.title
|
||||
if getBoard.archived
|
||||
i.fa.fa-archive
|
||||
li.result-card-context.result-card-context-separator
|
||||
= ' '
|
||||
| {{_ 'context-separator'}}
|
||||
= ' '
|
||||
li.result-card-context(title="{{_ 'swimlane'}}")
|
||||
+viewer
|
||||
= getSwimlane.title
|
||||
.result-card-block-wrapper
|
||||
+viewer
|
||||
= getSwimlane.title
|
||||
if getSwimlane.archived
|
||||
i.fa.fa-archive
|
||||
li.result-card-context.result-card-context-separator
|
||||
= ' '
|
||||
| {{_ 'context-separator'}}
|
||||
= ' '
|
||||
li.result-card-context(title="{{_ 'list'}}")
|
||||
+viewer
|
||||
= getList.title
|
||||
.result-card-block-wrapper
|
||||
+viewer
|
||||
= getList.title
|
||||
if getList.archived
|
||||
i.fa.fa-archive
|
||||
@@ -19,3 +19,6 @@
|
||||
|
||||
.result-card-context-list
|
||||
margin-bottom: 0.7rem
|
||||
|
||||
.result-card-block-wrapper
|
||||
display: inline-block
|
||||
@@ -239,7 +239,7 @@ BlazeComponent.extendComponent({
|
||||
.customFields()
|
||||
.fetch(),
|
||||
function(field) {
|
||||
if (field.automaticallyOnCard)
|
||||
if (field.automaticallyOnCard || field.alwaysOnCard)
|
||||
arr.push({ _id: field._id, value: null });
|
||||
},
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ template(name="listHeader")
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if reachedWipLimit}}highlight{{/if}}") {{cards.count}}
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.count}}
|
||||
|/#{wipLimit.value})
|
||||
|
||||
if showCardsCountForList cards.count
|
||||
|
||||
@@ -72,6 +72,14 @@ BlazeComponent.extendComponent({
|
||||
);
|
||||
},
|
||||
|
||||
exceededWipLimit() {
|
||||
const list = Template.currentData();
|
||||
return (
|
||||
list.getWipLimit('enabled') &&
|
||||
list.getWipLimit('value') < list.cards().count()
|
||||
);
|
||||
},
|
||||
|
||||
showCardsCountForList(count) {
|
||||
const limit = this.limitToShowCardsCount();
|
||||
return limit > 0 && count > limit;
|
||||
|
||||
@@ -278,6 +278,8 @@ function mySafeAttrValue(tag, name, value, cssFilter) {
|
||||
/^aodroplink:/gi.test(value) ||
|
||||
/^onenote:/gi.test(value) ||
|
||||
/^file:/gi.test(value) ||
|
||||
/^abasurl:/gi.test(value) ||
|
||||
/^conisio:/gi.test(value) ||
|
||||
/^mailspring:/gi.test(value)
|
||||
) {
|
||||
return value;
|
||||
|
||||
@@ -30,13 +30,23 @@ template(name="globalSearch")
|
||||
div
|
||||
each msg in errorMessages
|
||||
span.global-search-error-messages
|
||||
| {{_ msg.tag msg.value }}
|
||||
= msg
|
||||
else
|
||||
h1
|
||||
= resultsHeading.get
|
||||
a.fa.fa-link(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
|
||||
each card in results
|
||||
each card in results.get
|
||||
+resultCard(card)
|
||||
table.global-search-footer
|
||||
tr
|
||||
td.global-search-previous-page
|
||||
if hasPreviousPage.get
|
||||
button.js-previous-page
|
||||
| {{_ 'previous-page' }}
|
||||
td.global-search-next-page(align="right")
|
||||
if hasNextPage.get
|
||||
button.js-next-page
|
||||
| {{_ 'next-page' }}
|
||||
else
|
||||
.global-search-instructions
|
||||
h2 {{_ 'boards' }}
|
||||
|
||||
@@ -45,19 +45,16 @@ BlazeComponent.extendComponent({
|
||||
this.myLists = new ReactiveVar([]);
|
||||
this.myLabelNames = new ReactiveVar([]);
|
||||
this.myBoardNames = new ReactiveVar([]);
|
||||
this.results = new ReactiveVar([]);
|
||||
this.hasNextPage = new ReactiveVar(false);
|
||||
this.hasPreviousPage = new ReactiveVar(false);
|
||||
this.queryParams = null;
|
||||
this.parsingErrors = [];
|
||||
this.resultsCount = 0;
|
||||
this.totalHits = 0;
|
||||
this.queryErrors = null;
|
||||
this.colorMap = null;
|
||||
// this.colorMap = {};
|
||||
// for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
// .allowedValues) {
|
||||
// this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
// }
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('colorMap:', this.colorMap);
|
||||
this.resultsPerPage = 25;
|
||||
|
||||
Meteor.call('myLists', (err, data) => {
|
||||
if (!err) {
|
||||
@@ -76,8 +73,19 @@ BlazeComponent.extendComponent({
|
||||
this.myBoardNames.set(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
Meteor.subscribe('setting');
|
||||
|
||||
this.colorMap = {};
|
||||
for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
.allowedValues) {
|
||||
this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
}
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('colorMap:', this.colorMap);
|
||||
|
||||
if (Session.get('globalQuery')) {
|
||||
this.searchAllBoards(Session.get('globalQuery'));
|
||||
}
|
||||
@@ -85,6 +93,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
resetSearch() {
|
||||
this.searching.set(false);
|
||||
this.results.set([]);
|
||||
this.hasResults.set(false);
|
||||
this.hasQueryErrors.set(false);
|
||||
this.resultsHeading.set('');
|
||||
@@ -94,79 +103,83 @@ BlazeComponent.extendComponent({
|
||||
this.queryErrors = null;
|
||||
},
|
||||
|
||||
results() {
|
||||
getSessionData() {
|
||||
return SessionData.findOne({
|
||||
userId: Meteor.userId(),
|
||||
sessionId: SessionData.getSessionId(),
|
||||
});
|
||||
},
|
||||
|
||||
getResults() {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('getting results');
|
||||
if (this.queryParams) {
|
||||
const results = Cards.globalSearch(this.queryParams);
|
||||
this.queryErrors = results.errors;
|
||||
const sessionData = this.getSessionData();
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('errors:', this.queryErrors);
|
||||
if (this.errorMessages().length) {
|
||||
console.log('selector:', sessionData.getSelector());
|
||||
// console.log('session data:', sessionData);
|
||||
const cards = Cards.find({ _id: { $in: sessionData.cards } });
|
||||
this.queryErrors = sessionData.errors;
|
||||
if (this.queryErrors.length) {
|
||||
this.hasQueryErrors.set(true);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (results.cards) {
|
||||
const sessionData = SessionData.findOne({ userId: Meteor.userId() });
|
||||
if (cards) {
|
||||
this.totalHits = sessionData.totalHits;
|
||||
this.resultsCount = results.cards.count();
|
||||
this.resultsCount = cards.count();
|
||||
this.resultsStart = sessionData.lastHit - this.resultsCount + 1;
|
||||
this.resultsEnd = sessionData.lastHit;
|
||||
this.resultsHeading.set(this.getResultsHeading());
|
||||
return results.cards;
|
||||
this.results.set(cards);
|
||||
this.hasNextPage.set(sessionData.lastHit < sessionData.totalHits);
|
||||
this.hasPreviousPage.set(
|
||||
sessionData.lastHit - sessionData.resultsCount > 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.resultsCount = 0;
|
||||
return [];
|
||||
return null;
|
||||
},
|
||||
|
||||
errorMessages() {
|
||||
const messages = [];
|
||||
|
||||
if (this.queryErrors) {
|
||||
this.queryErrors.notFound.boards.forEach(board => {
|
||||
messages.push({ tag: 'board-title-not-found', value: board });
|
||||
});
|
||||
this.queryErrors.notFound.swimlanes.forEach(swim => {
|
||||
messages.push({ tag: 'swimlane-title-not-found', value: swim });
|
||||
});
|
||||
this.queryErrors.notFound.lists.forEach(list => {
|
||||
messages.push({ tag: 'list-title-not-found', value: list });
|
||||
});
|
||||
this.queryErrors.notFound.labels.forEach(label => {
|
||||
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[0],
|
||||
});
|
||||
} else {
|
||||
messages.push({ tag: 'label-not-found', value: label });
|
||||
}
|
||||
});
|
||||
this.queryErrors.notFound.users.forEach(user => {
|
||||
messages.push({ tag: 'user-username-not-found', value: user });
|
||||
});
|
||||
this.queryErrors.notFound.members.forEach(user => {
|
||||
messages.push({ tag: 'user-username-not-found', value: user });
|
||||
});
|
||||
this.queryErrors.notFound.assignees.forEach(user => {
|
||||
messages.push({ tag: 'user-username-not-found', value: user });
|
||||
});
|
||||
if (this.parsingErrors.length) {
|
||||
return this.parsingErrorMessages();
|
||||
}
|
||||
return this.queryErrorMessages();
|
||||
},
|
||||
|
||||
parsingErrorMessages() {
|
||||
const messages = [];
|
||||
|
||||
if (this.parsingErrors.length) {
|
||||
this.parsingErrors.forEach(err => {
|
||||
messages.push(err);
|
||||
messages.push(TAPi18n.__(err.tag, err.value));
|
||||
});
|
||||
}
|
||||
|
||||
return messages;
|
||||
},
|
||||
|
||||
queryErrorMessages() {
|
||||
messages = [];
|
||||
|
||||
this.queryErrors.forEach(err => {
|
||||
let value = err.color ? TAPi18n.__(`color-${err.value}`) : err.value;
|
||||
if (!value) {
|
||||
value = err.value;
|
||||
}
|
||||
messages.push(TAPi18n.__(err.tag, value));
|
||||
});
|
||||
|
||||
return messages;
|
||||
},
|
||||
|
||||
searchAllBoards(query) {
|
||||
query = query.trim();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('query:', query);
|
||||
|
||||
this.query.set(query);
|
||||
|
||||
this.resetSearch();
|
||||
@@ -175,47 +188,75 @@ BlazeComponent.extendComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('query:', query);
|
||||
|
||||
this.searching.set(true);
|
||||
|
||||
if (!this.colorMap) {
|
||||
this.colorMap = {};
|
||||
for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
.allowedValues) {
|
||||
this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
}
|
||||
}
|
||||
const reOperator1 = `/^((?<operator>[\p{Letter}\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\p{Letter}\p{Mark}]+)(\s+|$)/iu`;
|
||||
const reOperator2 = `/^((?<operator>[\p{Letter}\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/iu`;
|
||||
const reText = `/^(?<text>\S+)(\s+|$)/u`;
|
||||
const reQuotedText = `/^(?<quote>["'])(?<text>[\w\p{L}]+)\k<quote>(\s+|$)/u`;
|
||||
|
||||
const reOperator1 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<value>\w+)(\s+|$)/;
|
||||
const reOperator2 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/;
|
||||
const reText = /^(?<text>\S+)(\s+|$)/;
|
||||
const reQuotedText = /^(?<quote>["'])(?<text>\w+)\k<quote>(\s+|$)/;
|
||||
const operators = {
|
||||
'operator-board': 'boards',
|
||||
'operator-board-abbrev': 'boards',
|
||||
'operator-swimlane': 'swimlanes',
|
||||
'operator-swimlane-abbrev': 'swimlanes',
|
||||
'operator-list': 'lists',
|
||||
'operator-list-abbrev': 'lists',
|
||||
'operator-label': 'labels',
|
||||
'operator-label-abbrev': 'labels',
|
||||
'operator-user': 'users',
|
||||
'operator-user-abbrev': 'users',
|
||||
'operator-member': 'members',
|
||||
'operator-member-abbrev': 'members',
|
||||
'operator-assignee': 'assignees',
|
||||
'operator-assignee-abbrev': 'assignees',
|
||||
'operator-status': 'status',
|
||||
'operator-due': 'dueAt',
|
||||
'operator-created': 'createdAt',
|
||||
'operator-modified': 'modifiedAt',
|
||||
'operator-comment': 'comments',
|
||||
};
|
||||
|
||||
const predicates = {
|
||||
due: {
|
||||
'predicate-overdue': 'overdue',
|
||||
},
|
||||
durations: {
|
||||
'predicate-week': 'week',
|
||||
'predicate-month': 'month',
|
||||
'predicate-quarter': 'quarter',
|
||||
'predicate-year': 'year',
|
||||
},
|
||||
status: {
|
||||
'predicate-archived': 'archived',
|
||||
'predicate-all': 'all',
|
||||
'predicate-ended': 'ended',
|
||||
},
|
||||
sorts: {
|
||||
'predicate-due': 'dueAt',
|
||||
'predicate-created': 'createdAt',
|
||||
'predicate-modified': 'modifiedAt',
|
||||
},
|
||||
};
|
||||
const predicateTranslations = {};
|
||||
Object.entries(predicates).forEach(([category, catPreds]) => {
|
||||
predicateTranslations[category] = {};
|
||||
Object.entries(catPreds).forEach(([tag, value]) => {
|
||||
predicateTranslations[category][TAPi18n.__(tag)] = value;
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('predicateTranslations:', predicateTranslations);
|
||||
|
||||
const operatorMap = {};
|
||||
operatorMap[TAPi18n.__('operator-board')] = 'boards';
|
||||
operatorMap[TAPi18n.__('operator-board-abbrev')] = 'boards';
|
||||
operatorMap[TAPi18n.__('operator-swimlane')] = 'swimlanes';
|
||||
operatorMap[TAPi18n.__('operator-swimlane-abbrev')] = 'swimlanes';
|
||||
operatorMap[TAPi18n.__('operator-list')] = 'lists';
|
||||
operatorMap[TAPi18n.__('operator-list-abbrev')] = 'lists';
|
||||
operatorMap[TAPi18n.__('operator-label')] = 'labels';
|
||||
operatorMap[TAPi18n.__('operator-label-abbrev')] = 'labels';
|
||||
operatorMap[TAPi18n.__('operator-user')] = 'users';
|
||||
operatorMap[TAPi18n.__('operator-user-abbrev')] = 'users';
|
||||
operatorMap[TAPi18n.__('operator-member')] = 'members';
|
||||
operatorMap[TAPi18n.__('operator-member-abbrev')] = 'members';
|
||||
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';
|
||||
|
||||
Object.entries(operators).forEach(([key, value]) => {
|
||||
operatorMap[TAPi18n.__(key).toLowerCase()] = value;
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('operatorMap:', operatorMap);
|
||||
// console.log('operatorMap:', operatorMap);
|
||||
|
||||
const params = {
|
||||
limit: this.resultsPerPage,
|
||||
boards: [],
|
||||
swimlanes: [],
|
||||
lists: [],
|
||||
@@ -223,10 +264,11 @@ BlazeComponent.extendComponent({
|
||||
members: [],
|
||||
assignees: [],
|
||||
labels: [],
|
||||
is: [],
|
||||
status: [],
|
||||
dueAt: null,
|
||||
createdAt: null,
|
||||
modifiedAt: null,
|
||||
comments: [],
|
||||
};
|
||||
|
||||
let text = '';
|
||||
@@ -247,46 +289,73 @@ BlazeComponent.extendComponent({
|
||||
} else {
|
||||
op = m.groups.abbrev;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (operatorMap.hasOwnProperty(op)) {
|
||||
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])
|
||||
) {
|
||||
let days = parseInt(value, 10);
|
||||
let duration = null;
|
||||
if (isNaN(days)) {
|
||||
if (predicateTranslations.durations[value]) {
|
||||
duration = predicateTranslations.durations[value];
|
||||
value = moment();
|
||||
} else if (predicateTranslations.due[value] === 'overdue') {
|
||||
value = moment();
|
||||
duration = 'days';
|
||||
days = 0;
|
||||
} else {
|
||||
value = moment()
|
||||
.subtract(days, 'days')
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-number-expected',
|
||||
value: { operator: op, value },
|
||||
});
|
||||
value = null;
|
||||
}
|
||||
} else {
|
||||
value = moment();
|
||||
}
|
||||
if (value) {
|
||||
if (operatorMap[op] === 'dueAt') {
|
||||
value = value.add(days, duration ? duration : 'days').format();
|
||||
} else {
|
||||
value = value
|
||||
.subtract(days, duration ? duration : 'days')
|
||||
.format();
|
||||
}
|
||||
}
|
||||
if (Array.isArray(params[operatorMap[op]])) {
|
||||
params[operatorMap[op]].push(value);
|
||||
} else if (operatorMap[op] === 'sort') {
|
||||
if (!predicateTranslations.sorts[value]) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-sort-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
params[operatorMap[op]] = value;
|
||||
value = predicateTranslations.sorts[value];
|
||||
}
|
||||
} else if (operatorMap[op] === 'status') {
|
||||
if (!predicateTranslations.status[value]) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-status-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
value = predicateTranslations.status[value];
|
||||
}
|
||||
} else {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-unknown-error',
|
||||
value: op,
|
||||
});
|
||||
}
|
||||
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,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -314,13 +383,79 @@ BlazeComponent.extendComponent({
|
||||
|
||||
this.queryParams = params;
|
||||
|
||||
if (this.parsingErrors.length) {
|
||||
this.searching.set(false);
|
||||
this.queryErrors = this.parsingErrorMessages();
|
||||
this.hasResults.set(true);
|
||||
this.hasQueryErrors.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = subManager.subscribe('globalSearch', params);
|
||||
const handle = Meteor.subscribe(
|
||||
'globalSearch',
|
||||
SessionData.getSessionId(),
|
||||
params,
|
||||
);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('ready:', handle.ready());
|
||||
if (handle.ready()) {
|
||||
this.getResults();
|
||||
this.searching.set(false);
|
||||
this.hasResults.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
sessionData = this.getSessionData();
|
||||
|
||||
const params = {
|
||||
limit: this.resultsPerPage,
|
||||
selector: sessionData.getSelector(),
|
||||
skip: sessionData.lastHit,
|
||||
};
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = Meteor.subscribe(
|
||||
'globalSearch',
|
||||
SessionData.getSessionId(),
|
||||
params,
|
||||
);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
this.getResults();
|
||||
this.searching.set(false);
|
||||
this.hasResults.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
previousPage() {
|
||||
sessionData = this.getSessionData();
|
||||
|
||||
const params = {
|
||||
limit: this.resultsPerPage,
|
||||
selector: sessionData.getSelector(),
|
||||
skip:
|
||||
sessionData.lastHit - sessionData.resultsCount - this.resultsPerPage,
|
||||
};
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = Meteor.subscribe(
|
||||
'globalSearch',
|
||||
SessionData.getSessionId(),
|
||||
params,
|
||||
);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
this.getResults();
|
||||
this.searching.set(false);
|
||||
this.hasResults.set(true);
|
||||
}
|
||||
@@ -339,8 +474,8 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
|
||||
return TAPi18n.__('n-n-of-n-cards-found', {
|
||||
start: 1,
|
||||
end: this.resultsCount,
|
||||
start: this.resultsStart,
|
||||
end: this.resultsEnd,
|
||||
total: this.totalHits,
|
||||
});
|
||||
},
|
||||
@@ -355,6 +490,7 @@ BlazeComponent.extendComponent({
|
||||
operator_board: TAPi18n.__('operator-board'),
|
||||
operator_list: TAPi18n.__('operator-list'),
|
||||
operator_swimlane: TAPi18n.__('operator-swimlane'),
|
||||
operator_comment: TAPi18n.__('operator-comment'),
|
||||
operator_label: TAPi18n.__('operator-label'),
|
||||
operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
|
||||
operator_user: TAPi18n.__('operator-user'),
|
||||
@@ -363,6 +499,18 @@ BlazeComponent.extendComponent({
|
||||
operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
|
||||
operator_assignee: TAPi18n.__('operator-assignee'),
|
||||
operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
|
||||
operator_due: TAPi18n.__('operator-due'),
|
||||
operator_created: TAPi18n.__('operator-created'),
|
||||
operator_modified: TAPi18n.__('operator-modified'),
|
||||
operator_status: TAPi18n.__('operator-status'),
|
||||
predicate_overdue: TAPi18n.__('predicate-overdue'),
|
||||
predicate_archived: TAPi18n.__('predicate-archived'),
|
||||
predicate_all: TAPi18n.__('predicate-all'),
|
||||
predicate_ended: TAPi18n.__('predicate-ended'),
|
||||
predicate_week: TAPi18n.__('predicate-week'),
|
||||
predicate_month: TAPi18n.__('predicate-month'),
|
||||
predicate_quarter: TAPi18n.__('predicate-quarter'),
|
||||
predicate_year: TAPi18n.__('predicate-year'),
|
||||
};
|
||||
|
||||
text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
|
||||
@@ -380,6 +528,10 @@ BlazeComponent.extendComponent({
|
||||
'globalSearch-instructions-operator-swimlane',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__(
|
||||
'globalSearch-instructions-operator-comment',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__(
|
||||
'globalSearch-instructions-operator-label',
|
||||
tags,
|
||||
@@ -401,11 +553,27 @@ BlazeComponent.extendComponent({
|
||||
'globalSearch-instructions-operator-assignee',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-operator-due', tags)}`;
|
||||
text += `\n* ${TAPi18n.__(
|
||||
'globalSearch-instructions-operator-created',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__(
|
||||
'globalSearch-instructions-operator-modified',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__(
|
||||
'globalSearch-instructions-status-archived',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-status-all', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-status-ended', tags)}`;
|
||||
|
||||
text += `\n## ${TAPi18n.__('heading-notes')}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-1', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-2', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3-2', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-4', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-5', tags)}`;
|
||||
|
||||
@@ -427,10 +595,19 @@ BlazeComponent.extendComponent({
|
||||
evt.preventDefault();
|
||||
this.searchAllBoards(evt.target.searchQuery.value);
|
||||
},
|
||||
'click .js-next-page'(evt) {
|
||||
evt.preventDefault();
|
||||
this.nextPage();
|
||||
},
|
||||
'click .js-previous-page'(evt) {
|
||||
evt.preventDefault();
|
||||
this.previousPage();
|
||||
},
|
||||
'click .js-label-color'(evt) {
|
||||
evt.preventDefault();
|
||||
const input = document.getElementById('global-search-input');
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-label')}:"${
|
||||
`${input.value} ${TAPi18n.__('operator-label')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
@@ -438,8 +615,9 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
'click .js-board-title'(evt) {
|
||||
evt.preventDefault();
|
||||
const input = document.getElementById('global-search-input');
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-board')}:"${
|
||||
`${input.value} ${TAPi18n.__('operator-board')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
@@ -447,8 +625,9 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
'click .js-list-title'(evt) {
|
||||
evt.preventDefault();
|
||||
const input = document.getElementById('global-search-input');
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-list')}:"${
|
||||
`${input.value} ${TAPi18n.__('operator-list')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
@@ -456,8 +635,9 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
'click .js-label-name'(evt) {
|
||||
evt.preventDefault();
|
||||
const input = document.getElementById('global-search-input');
|
||||
this.query.set(
|
||||
`${this.query.get()} ${TAPi18n.__('operator-label')}:"${
|
||||
`${input.value} ${TAPi18n.__('operator-label')}:"${
|
||||
evt.currentTarget.textContent
|
||||
}"`,
|
||||
);
|
||||
|
||||
@@ -104,3 +104,15 @@ code
|
||||
|
||||
.list-title
|
||||
background-color: darkgray
|
||||
|
||||
.global-search-footer
|
||||
border: none
|
||||
width: 100%
|
||||
|
||||
.global-search-next-page
|
||||
border: none
|
||||
text-align: right;
|
||||
|
||||
.global-search-previous-page
|
||||
border: none
|
||||
text-align: left;
|
||||
@@ -7,16 +7,21 @@ template(name="header")
|
||||
if currentUser
|
||||
#header-quick-access(class=currentBoard.colorClass)
|
||||
if isMiniScreen
|
||||
ul
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
span
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
|
||||
ul
|
||||
if currentList
|
||||
each currentBoard.lists
|
||||
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
|
||||
a.js-select-list
|
||||
= title
|
||||
else
|
||||
each currentUser.starredBoards
|
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
= title
|
||||
#header-new-board-icon
|
||||
else
|
||||
//-
|
||||
@@ -32,18 +37,16 @@ template(name="header")
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0")
|
||||
unless currentSetting.customTopLeftCornerLogoImageUrl
|
||||
img(src="{{pathFor '/logo-header.png'}}" alt="")
|
||||
span
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
ul
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
li.separator -
|
||||
//li
|
||||
// a(href="{{pathFor 'public'}}")
|
||||
// span.fa.fa-globe
|
||||
// | {{_ 'public'}}
|
||||
each currentUser.starredBoards
|
||||
li.separator -
|
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
= title
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
font-size: 12px
|
||||
display: flex
|
||||
z-index: 21
|
||||
padding: 10px 0px
|
||||
|
||||
#header-user-bar,
|
||||
#header-new-board-icon,
|
||||
@@ -114,15 +115,22 @@
|
||||
|
||||
ul
|
||||
transition: opacity 0.2s
|
||||
margin: 4px 0 0 5px
|
||||
overflow: hidden
|
||||
overflow-x: auto
|
||||
overflow-y: hidden
|
||||
white-space: nowrap
|
||||
padding: 10px
|
||||
margin: -10px
|
||||
|
||||
li
|
||||
display: block
|
||||
float: left
|
||||
display: inline
|
||||
width: auto
|
||||
color: darken(white, 15%)
|
||||
padding: 2px 5px 0
|
||||
padding: 12px 0px
|
||||
margin: -10px 0px
|
||||
|
||||
a
|
||||
padding: 12px 10px
|
||||
margin: -10px 0px
|
||||
|
||||
&.current
|
||||
color: darken(white, 5%)
|
||||
@@ -186,37 +194,31 @@
|
||||
#header-quick-access
|
||||
transition: background-color 0.4s
|
||||
width: 100%
|
||||
padding: 10px 0px
|
||||
z-index: 30
|
||||
|
||||
ul
|
||||
width: calc(100% - 60px)
|
||||
overflow: ellipsis
|
||||
padding: 10px
|
||||
margin: -10px
|
||||
margin-right: 10px
|
||||
|
||||
li
|
||||
height: 100%
|
||||
padding: 12px 0px
|
||||
margin: -10px 0px
|
||||
|
||||
a
|
||||
height: 100%
|
||||
padding: 12px 10px
|
||||
margin: -10px 0px
|
||||
|
||||
.fa-home
|
||||
span
|
||||
.fa-home
|
||||
font-size: 26px
|
||||
margin-top: -2px
|
||||
margin-right: 20px
|
||||
|
||||
#header-new-board-icon
|
||||
display: none
|
||||
|
||||
#header-user-bar
|
||||
position: absolute
|
||||
right: 0px
|
||||
padding: 10px
|
||||
margin: -10px 0 -10px -10px
|
||||
margin: -8px 0 -10px -10px
|
||||
|
||||
.announcement .viewer
|
||||
display: inline-block
|
||||
|
||||
@@ -29,9 +29,10 @@ template(name="myCards")
|
||||
if $eq myCardsSort 'board'
|
||||
each board in myCardsList
|
||||
.my-cards-board-wrapper
|
||||
.my-cards-board-title
|
||||
+viewer
|
||||
= board.title
|
||||
.my-cards-board-title(class=board.colorClass, id="header")
|
||||
a(href=board.absoluteUrl)
|
||||
+viewer
|
||||
= board.title
|
||||
each swimlane in board.mySwimlanes
|
||||
.my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
|
||||
+viewer
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
.my-cards-board-wrapper
|
||||
border-radius: 8px
|
||||
border-radius: 0 0 4px 4px;
|
||||
//padding: 0.5rem
|
||||
min-width: 400px
|
||||
border-width: 8px
|
||||
border-color: grey
|
||||
border-style: solid
|
||||
margin-bottom: 2rem
|
||||
margin-right: auto
|
||||
margin-left: auto
|
||||
|
||||
border-width: 2px
|
||||
border-style: solid
|
||||
border-color: #a2a2a2
|
||||
|
||||
.my-cards-board-title
|
||||
font-size: 1.4rem
|
||||
font-weight: bold
|
||||
@@ -41,9 +41,9 @@
|
||||
.my-cards-list-wrapper
|
||||
margin: 1rem
|
||||
border-radius: 5px
|
||||
padding: 1.5rem
|
||||
padding-top: 0.75rem
|
||||
display: table-cell
|
||||
//padding: 1.5rem
|
||||
//padding-top: 0.75rem
|
||||
display: inline-grid
|
||||
min-width: 250px
|
||||
max-width: 350px
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ template(name="createCustomFieldPopup")
|
||||
|
||||
span {{_ 'automatically-field-on-card'}}
|
||||
|
||||
a.flex.js-field-always-on-card(class="{{#if alwaysOnCard}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if alwaysOnCard}}is-checked{{/if}}")
|
||||
span {{_ 'always-field-on-card'}}
|
||||
|
||||
a.flex.js-field-showLabel-on-card(class="{{#if showLabelOnMiniCard}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if showLabelOnMiniCard}}is-checked{{/if}}")
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ BlazeComponent.extendComponent({
|
||||
}).register('customFieldsSidebar');
|
||||
|
||||
const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
_types: ['text', 'number', 'date', 'dropdown', 'currency'],
|
||||
_types: ['text', 'number', 'date', 'dropdown', 'currency', 'checkbox'],
|
||||
|
||||
_currencyList: [
|
||||
{
|
||||
@@ -174,6 +174,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
$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')) {
|
||||
@@ -194,6 +202,8 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
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,
|
||||
};
|
||||
|
||||
// insert or update
|
||||
|
||||
+12
-9
@@ -95,7 +95,10 @@ services:
|
||||
# 2020-12-03:
|
||||
# - Mongo image copied from Docker Hub mongo:4.4.2-bionic to Quay
|
||||
# to avoid Docker Hub rate limits.
|
||||
image: quay.io/wekan/mongo:4.4.2-bionic
|
||||
# Quay image
|
||||
# image: quay.io/wekan/mongo:4.4.2-bionic
|
||||
# Docker Hub MongoDB image works:
|
||||
image: mongo:4.4
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-db
|
||||
restart: always
|
||||
@@ -114,11 +117,11 @@ services:
|
||||
wekan:
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== MONGODB AND METEOR VERSION ====
|
||||
# a) Quay automatic builds do work, https://quay.io/wekan7wekan
|
||||
image: quay.io/wekan/wekan
|
||||
# a) Quay automatic builds do not currently work, https://quay.io/wekan/wekan
|
||||
# image: quay.io/wekan/wekan
|
||||
# b) Using specific version tag:
|
||||
# image: quay.io/wekan/wekan:v4.52
|
||||
# c) Docker Hub builds work https://hub.docker.com/r/wekanteam/wekan
|
||||
c) Docker Hub builds do work https://hub.docker.com/r/wekanteam/wekan
|
||||
# image: wekanteam/wekan
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-app
|
||||
@@ -457,8 +460,8 @@ services:
|
||||
#
|
||||
# The search user DN - You need quotes when you have spaces in parameters
|
||||
# 2 examples:
|
||||
#- LDAP_AUTHENTIFICATION_USERDN="CN=ldap admin,CN=users,DC=domainmatter,DC=lan"
|
||||
#- LDAP_AUTHENTIFICATION_USERDN="CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com"
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=CN=ldap admin,CN=users,DC=domainmatter,DC=lan
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com
|
||||
#
|
||||
# The password for the search user
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD=pwd
|
||||
@@ -488,10 +491,10 @@ services:
|
||||
#- LDAP_REJECT_UNAUTHORIZED=false
|
||||
#
|
||||
# Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
|
||||
#- LDAP_USER_AUTHENTICATION="true"
|
||||
#- LDAP_USER_AUTHENTICATION=true
|
||||
#
|
||||
# Which field is used to find the user for the user authentication. Default: uid.
|
||||
#- LDAP_USER_AUTHENTICATION_FIELD="uid"
|
||||
#- LDAP_USER_AUTHENTICATION_FIELD=uid
|
||||
#
|
||||
# Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
#- LDAP_USER_SEARCH_FILTER=
|
||||
@@ -556,7 +559,7 @@ services:
|
||||
#
|
||||
#- LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
|
||||
#
|
||||
#- LDAP_SYNC_GROUP_ROLES=''
|
||||
#- LDAP_SYNC_GROUP_ROLES=
|
||||
#
|
||||
# The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
# example :
|
||||
|
||||
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+30
-3
@@ -601,7 +601,8 @@
|
||||
"minutes": "minutes",
|
||||
"seconds": "seconds",
|
||||
"show-field-on-card": "Show this field on card",
|
||||
"automatically-field-on-card": "Auto create field to all cards",
|
||||
"automatically-field-on-card": "Add field to new cards",
|
||||
"always-field-on-card": "Add field to all cards",
|
||||
"showLabel-field-on-card": "Show field label on minicard",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
@@ -875,6 +876,7 @@
|
||||
"label-not-found": "Label '%s' not found.",
|
||||
"label-color-not-found": "Label color %s not found.",
|
||||
"user-username-not-found": "Username '%s' not found.",
|
||||
"comment-not-found": "Card with comment containing text '%s' not found.",
|
||||
"globalSearch-title": "Search All Boards",
|
||||
"no-cards-found": "No Cards Found",
|
||||
"one-card-found": "One Card Found",
|
||||
@@ -894,12 +896,29 @@
|
||||
"operator-member-abbrev": "m",
|
||||
"operator-assignee": "assignee",
|
||||
"operator-assignee-abbrev": "a",
|
||||
"operator-is": "is",
|
||||
"operator-status": "status",
|
||||
"operator-due": "due",
|
||||
"operator-created": "created",
|
||||
"operator-modified": "modified",
|
||||
"operator-sort": "sort",
|
||||
"operator-comment": "comment",
|
||||
"predicate-archived": "archived",
|
||||
"predicate-ended": "ended",
|
||||
"predicate-all": "all",
|
||||
"predicate-overdue": "overdue",
|
||||
"predicate-week": "week",
|
||||
"predicate-month": "month",
|
||||
"predicate-quarter": "quarter",
|
||||
"predicate-year": "year",
|
||||
"predicate-due": "due",
|
||||
"predicate-modified": "modified",
|
||||
"predicate-created": "created",
|
||||
"operator-unknown-error": "%s is not an operator",
|
||||
"operator-number-expected": "operator __operator__ expected a number, got '__value__'",
|
||||
"operator-sort-invalid": "sort of '%s' is invalid",
|
||||
"operator-status-invalid": "'%s' is not a valid status",
|
||||
"next-page": "Next Page",
|
||||
"previous-page": "Previous Page",
|
||||
"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\"`).",
|
||||
@@ -907,15 +926,23 @@
|
||||
"globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
|
||||
"globalSearch-instructions-operator-list": "`__operator_list__:title` - cards in lists matching the specified title",
|
||||
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:title` - cards in swimlanes matching the specified title",
|
||||
"globalSearch-instructions-operator-comment": "`__operator_comment__:text` - cards with with a comment containing *text*.",
|
||||
"globalSearch-instructions-operator-label": "`__operator_label__:color` `__operator_label__:name` - cards that have a label matching the given color or name",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__label` - shorthand for `__operator_label__:label`",
|
||||
"globalSearch-instructions-operator-user": "`__operator_user__:username` - cards where the specified user is a *member* or *assignee*",
|
||||
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:username`",
|
||||
"globalSearch-instructions-operator-member": "`__operator_member__:username` - cards where the specified user is a *member*",
|
||||
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:username` - cards where the specified user is an *assignee*",
|
||||
"globalSearch-instructions-operator-due": "`__operator_due__:n` - cards which are due *n* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.",
|
||||
"globalSearch-instructions-operator-created": "`__operator_created__:n` - cards which which were created *n* days ago",
|
||||
"globalSearch-instructions-operator-modified": "`__operator_modified__:n` - cards which which were modified *n* days ago",
|
||||
"globalSearch-instructions-status-archived": "`__operator_status__:__predicate_archived__` - cards that are archived.",
|
||||
"globalSearch-instructions-status-all": "`__operator_status__:__predicate_all__` - all archived and unarchived cards.",
|
||||
"globalSearch-instructions-status-ended": "`__operator_status__:__predicate_ended__` - cards with an end date.",
|
||||
"globalSearch-instructions-notes-1": "Multiple operators may be specified.",
|
||||
"globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
|
||||
"globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned.\n`__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
|
||||
"globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
|
||||
"globalSearch-instructions-notes-3-2": "Days can be specified as an integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__`",
|
||||
"globalSearch-instructions-notes-4": "Text searches are case insensitive.",
|
||||
"globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
|
||||
"link-to-search": "Link to this search",
|
||||
|
||||
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+951
-924
File diff suppressed because it is too large.
Load diff
+19
-22
@@ -1,3 +1,4 @@
|
||||
const escapeForRegex = require('escape-string-regexp');
|
||||
Boards = new Mongo.Collection('boards');
|
||||
|
||||
/**
|
||||
@@ -569,7 +570,7 @@ Boards.helpers({
|
||||
*/
|
||||
copyTitle() {
|
||||
const m = this.title.match(/^(?<title>.*?)\s*(\[(?<num>\d+)]\s*$|\s*$)/);
|
||||
const title = m.groups.title;
|
||||
const title = escapeForRegex(m.groups.title);
|
||||
let num = 0;
|
||||
Boards.find({ title: new RegExp(`^${title}\\s*\\[\\d+]\\s*$`) }).forEach(
|
||||
board => {
|
||||
@@ -1277,37 +1278,33 @@ Boards.userSearch = (
|
||||
userId,
|
||||
selector = {},
|
||||
projection = {},
|
||||
includeArchived = false,
|
||||
// includeArchived = false,
|
||||
) => {
|
||||
if (!includeArchived) {
|
||||
selector.archived = false;
|
||||
}
|
||||
selector.$or = [
|
||||
{ permission: 'public' },
|
||||
{ members: { $elemMatch: { userId, isActive: true } } },
|
||||
];
|
||||
// if (!includeArchived) {
|
||||
// selector.archived = false;
|
||||
// }
|
||||
selector.$or = [{ permission: 'public' }];
|
||||
|
||||
if (userId) {
|
||||
selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } });
|
||||
}
|
||||
return Boards.find(selector, projection);
|
||||
};
|
||||
|
||||
Boards.userBoards = (userId, includeArchived = false, selector = {}) => {
|
||||
check(userId, String);
|
||||
|
||||
if (!includeArchived) {
|
||||
selector = {
|
||||
archived: false,
|
||||
};
|
||||
Boards.userBoards = (userId, archived = false, selector = {}) => {
|
||||
if (typeof archived === 'boolean') {
|
||||
selector.archived = archived;
|
||||
}
|
||||
selector.$or = [
|
||||
{ permission: 'public' },
|
||||
{ members: { $elemMatch: { userId, isActive: true } } },
|
||||
];
|
||||
selector.$or = [{ permission: 'public' }];
|
||||
|
||||
if (userId) {
|
||||
selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } });
|
||||
}
|
||||
return Boards.find(selector);
|
||||
};
|
||||
|
||||
Boards.userBoardIds = (userId, includeArchived = false, selector = {}) => {
|
||||
return Boards.userBoards(userId, includeArchived, selector).map(board => {
|
||||
Boards.userBoardIds = (userId, archived = false, selector = {}) => {
|
||||
return Boards.userBoards(userId, archived, selector).map(board => {
|
||||
return board._id;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const escapeForRegex = require('escape-string-regexp');
|
||||
CardComments = new Mongo.Collection('card_comments');
|
||||
|
||||
/**
|
||||
@@ -109,6 +110,28 @@ function commentCreation(userId, doc) {
|
||||
});
|
||||
}
|
||||
|
||||
CardComments.textSearch = (userId, textArray) => {
|
||||
const selector = {
|
||||
boardId: { $in: Boards.userBoardIds(userId) },
|
||||
$and: [],
|
||||
};
|
||||
|
||||
for (const text of textArray) {
|
||||
selector.$and.push({ text: new RegExp(escapeForRegex(text)) });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('cardComments selector:', selector);
|
||||
|
||||
const comments = CardComments.find(selector);
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('count:', comments.count());
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('cards with comments:', comments.map(com => { return com.cardId }));
|
||||
|
||||
return comments;
|
||||
};
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// Comments are often fetched within a card, so we create an index to make these
|
||||
// queries more efficient.
|
||||
|
||||
-256
@@ -1863,262 +1863,6 @@ Cards.mutations({
|
||||
},
|
||||
});
|
||||
|
||||
Cards.globalSearch = queryParams => {
|
||||
const userId = Meteor.userId();
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('userId:', userId);
|
||||
|
||||
const errors = new (class {
|
||||
constructor() {
|
||||
this.notFound = {
|
||||
boards: [],
|
||||
swimlanes: [],
|
||||
lists: [],
|
||||
labels: [],
|
||||
users: [],
|
||||
members: [],
|
||||
assignees: [],
|
||||
is: [],
|
||||
};
|
||||
}
|
||||
|
||||
hasErrors() {
|
||||
for (const prop in this.notFound) {
|
||||
if (this.notFound[prop].length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
const selector = {
|
||||
archived: false,
|
||||
type: 'cardType-card',
|
||||
boardId: { $in: Boards.userBoardIds(userId) },
|
||||
swimlaneId: { $nin: Swimlanes.archivedSwimlaneIds() },
|
||||
listId: { $nin: Lists.archivedListIds() },
|
||||
};
|
||||
|
||||
if (queryParams.boards.length) {
|
||||
const queryBoards = [];
|
||||
queryParams.boards.forEach(query => {
|
||||
const boards = Boards.userSearch(userId, {
|
||||
title: new RegExp(query, 'i'),
|
||||
});
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
queryBoards.push(board._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.boards.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.boardId.$in = queryBoards;
|
||||
}
|
||||
|
||||
if (queryParams.swimlanes.length) {
|
||||
const querySwimlanes = [];
|
||||
queryParams.swimlanes.forEach(query => {
|
||||
const swimlanes = Swimlanes.find({
|
||||
title: new RegExp(query, 'i'),
|
||||
});
|
||||
if (swimlanes.count()) {
|
||||
swimlanes.forEach(swim => {
|
||||
querySwimlanes.push(swim._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.swimlanes.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.swimlaneId.$in = querySwimlanes;
|
||||
}
|
||||
|
||||
if (queryParams.lists.length) {
|
||||
const queryLists = [];
|
||||
queryParams.lists.forEach(query => {
|
||||
const lists = Lists.find({
|
||||
title: new RegExp(query, 'i'),
|
||||
});
|
||||
if (lists.count()) {
|
||||
lists.forEach(list => {
|
||||
queryLists.push(list._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.lists.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
queryParams.users.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryMembers.push(user._id);
|
||||
queryAssignees.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.users.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.members.length) {
|
||||
queryParams.members.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryMembers.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.members.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.assignees.length) {
|
||||
queryParams.assignees.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryAssignees.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.assignees.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryMembers.length && queryAssignees.length) {
|
||||
selector.$or = [
|
||||
{ members: { $in: queryMembers } },
|
||||
{ assignees: { $in: queryAssignees } },
|
||||
];
|
||||
} else if (queryMembers.length) {
|
||||
selector.members = { $in: queryMembers };
|
||||
} else if (queryAssignees.length) {
|
||||
selector.assignees = { $in: queryAssignees };
|
||||
}
|
||||
|
||||
if (queryParams.labels.length) {
|
||||
queryParams.labels.forEach(label => {
|
||||
const queryLabels = [];
|
||||
|
||||
let boards = Boards.userSearch(userId, {
|
||||
labels: { $elemMatch: { color: label.toLowerCase() } },
|
||||
});
|
||||
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board:', board);
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board.labels:', board.labels);
|
||||
board.labels
|
||||
.filter(boardLabel => {
|
||||
return boardLabel.color === label.toLowerCase();
|
||||
})
|
||||
.forEach(boardLabel => {
|
||||
queryLabels.push(boardLabel._id);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('label:', label);
|
||||
const reLabel = new RegExp(label, 'i');
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('reLabel:', reLabel);
|
||||
boards = Boards.userSearch(userId, {
|
||||
labels: { $elemMatch: { name: reLabel } },
|
||||
});
|
||||
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
board.labels
|
||||
.filter(boardLabel => {
|
||||
return boardLabel.name.match(reLabel);
|
||||
})
|
||||
.forEach(boardLabel => {
|
||||
queryLabels.push(boardLabel._id);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
errors.notFound.labels.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
selector.labelIds = { $in: queryLabels };
|
||||
});
|
||||
}
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
return { cards: null, errors };
|
||||
}
|
||||
|
||||
if (queryParams.text) {
|
||||
const regex = new RegExp(queryParams.text, 'i');
|
||||
|
||||
selector.$or = [
|
||||
{ title: regex },
|
||||
{ description: regex },
|
||||
{ customFields: { $elemMatch: { value: regex } } },
|
||||
];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('selector:', selector);
|
||||
const cards = Cards.find(selector, {
|
||||
fields: {
|
||||
_id: 1,
|
||||
archived: 1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
title: 1,
|
||||
type: 1,
|
||||
sort: 1,
|
||||
members: 1,
|
||||
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());
|
||||
|
||||
return { cards, errors };
|
||||
};
|
||||
|
||||
//FUNCTIONS FOR creation of Activities
|
||||
|
||||
function updateActivities(doc, fieldNames, modifier) {
|
||||
|
||||
@@ -147,6 +147,7 @@ export class CsvCreator {
|
||||
settings,
|
||||
showOnCard: false,
|
||||
automaticallyOnCard: false,
|
||||
alwaysOnCard: false,
|
||||
showLabelOnMiniCard: false,
|
||||
boardIds: [boardId],
|
||||
});
|
||||
|
||||
@@ -76,6 +76,12 @@ CustomFields.attachSchema(
|
||||
*/
|
||||
type: Boolean,
|
||||
},
|
||||
alwaysOnCard: {
|
||||
/**
|
||||
* should the custom field be automatically added to all cards?
|
||||
*/
|
||||
type: Boolean,
|
||||
},
|
||||
showLabelOnMiniCard: {
|
||||
/**
|
||||
* should the label of the custom field be shown on minicards?
|
||||
@@ -111,6 +117,19 @@ CustomFields.attachSchema(
|
||||
}),
|
||||
);
|
||||
|
||||
CustomFields.addToAllCards = cf => {
|
||||
Cards.update(
|
||||
{
|
||||
boardId: { $in: cf.boardIds },
|
||||
customFields: { $not: { $elemMatch: { _id: cf._id } } },
|
||||
},
|
||||
{
|
||||
$push: { customFields: { _id: cf._id, value: null } },
|
||||
},
|
||||
{ multi: true },
|
||||
);
|
||||
};
|
||||
|
||||
CustomFields.mutations({
|
||||
addBoard(boardId) {
|
||||
if (boardId) {
|
||||
@@ -198,6 +217,10 @@ if (Meteor.isServer) {
|
||||
|
||||
CustomFields.after.insert((userId, doc) => {
|
||||
customFieldCreation(userId, doc);
|
||||
|
||||
if (doc.alwaysOnCard) {
|
||||
CustomFields.addToAllCards(doc);
|
||||
}
|
||||
});
|
||||
|
||||
CustomFields.before.update((userId, doc, fieldNames, modifier) => {
|
||||
@@ -224,6 +247,11 @@ if (Meteor.isServer) {
|
||||
}
|
||||
});
|
||||
|
||||
CustomFields.after.update((userId, doc) => {
|
||||
if (doc.alwaysOnCard) {
|
||||
CustomFields.addToAllCards(doc);
|
||||
}
|
||||
});
|
||||
CustomFields.before.remove((userId, doc) => {
|
||||
customFieldDeletion(userId, doc);
|
||||
Activities.remove({
|
||||
|
||||
+200
-46
@@ -24,7 +24,6 @@ if (Meteor.isServer) {
|
||||
Picker.route('/api/boards/:boardId/exportExcel', function(params, req, res) {
|
||||
const boardId = params.boardId;
|
||||
let user = null;
|
||||
//console.log('Excel');
|
||||
|
||||
const loginToken = params.query.authToken;
|
||||
if (loginToken) {
|
||||
@@ -163,6 +162,11 @@ export class ExporterExcel {
|
||||
users[memberId] = true;
|
||||
});
|
||||
}
|
||||
if (card.assignees) {
|
||||
card.assignees.forEach(memberId => {
|
||||
users[memberId] = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
result.comments.forEach(comment => {
|
||||
users[comment.userId] = true;
|
||||
@@ -184,7 +188,6 @@ export class ExporterExcel {
|
||||
fields: {
|
||||
_id: 1,
|
||||
username: 1,
|
||||
'profile.fullname': 1,
|
||||
'profile.initials': 1,
|
||||
'profile.avatarUrl': 1,
|
||||
},
|
||||
@@ -199,7 +202,6 @@ export class ExporterExcel {
|
||||
return user;
|
||||
});
|
||||
|
||||
const jdata = result;
|
||||
//init exceljs workbook
|
||||
const Excel = require('exceljs');
|
||||
const workbook = new Excel.Workbook();
|
||||
@@ -208,9 +210,9 @@ export class ExporterExcel {
|
||||
workbook.created = new Date();
|
||||
workbook.modified = new Date();
|
||||
workbook.lastPrinted = new Date();
|
||||
const filename = `${jdata.title}.xlsx`;
|
||||
const filename = `${result.title}.xlsx`;
|
||||
//init worksheet
|
||||
const worksheet = workbook.addWorksheet(jdata.title, {
|
||||
const worksheet = workbook.addWorksheet(result.title, {
|
||||
properties: {
|
||||
tabColor: {
|
||||
argb: 'FFC0000',
|
||||
@@ -222,26 +224,26 @@ export class ExporterExcel {
|
||||
},
|
||||
});
|
||||
//get worksheet
|
||||
const ws = workbook.getWorksheet(jdata.title);
|
||||
const ws = workbook.getWorksheet(result.title);
|
||||
ws.properties.defaultRowHeight = 20;
|
||||
//init columns
|
||||
//Excel font. Western: Arial. zh-CN: 宋体
|
||||
ws.columns = [
|
||||
{
|
||||
key: 'a',
|
||||
width: 7,
|
||||
width: 14,
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
width: 16,
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'c',
|
||||
width: 7,
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'd',
|
||||
width: 14,
|
||||
width: 20,
|
||||
style: {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
@@ -252,7 +254,7 @@ export class ExporterExcel {
|
||||
},
|
||||
{
|
||||
key: 'e',
|
||||
width: 14,
|
||||
width: 20,
|
||||
style: {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
@@ -263,21 +265,88 @@ export class ExporterExcel {
|
||||
},
|
||||
{
|
||||
key: 'f',
|
||||
width: 10,
|
||||
width: 20,
|
||||
style: {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
size: '10',
|
||||
},
|
||||
numFmt: 'yyyy/mm/dd hh:mm:ss',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'g',
|
||||
width: 10,
|
||||
width: 20,
|
||||
style: {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
size: '10',
|
||||
},
|
||||
numFmt: 'yyyy/mm/dd hh:mm:ss',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'h',
|
||||
width: 18,
|
||||
width: 20,
|
||||
style: {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
size: '10',
|
||||
},
|
||||
numFmt: 'yyyy/mm/dd hh:mm:ss',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'i',
|
||||
width: 20,
|
||||
style: {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
size: '10',
|
||||
},
|
||||
numFmt: 'yyyy/mm/dd hh:mm:ss',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'j',
|
||||
width: 20,
|
||||
style: {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
size: '10',
|
||||
},
|
||||
numFmt: 'yyyy/mm/dd hh:mm:ss',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'k',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'l',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'm',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'n',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'o',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
key: 'p',
|
||||
width: 20,
|
||||
},
|
||||
];
|
||||
|
||||
//add title line
|
||||
ws.mergeCells('A1:H1');
|
||||
ws.getCell('A1').value = jdata.title;
|
||||
ws.getCell('A1').value = result.title;
|
||||
ws.getCell('A1').style = {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
@@ -289,38 +358,54 @@ export class ExporterExcel {
|
||||
horizontal: 'center',
|
||||
};
|
||||
ws.getRow(1).height = 40;
|
||||
//get member info
|
||||
//get member and assignee info
|
||||
let jmem = '';
|
||||
let jassig = '';
|
||||
const jmeml = {};
|
||||
for (const i in jdata.users) {
|
||||
jmem = `${jmem + jdata.users[i].profile.fullname},`;
|
||||
jmeml[jdata.users[i]._id] = jdata.users[i].profile.fullname;
|
||||
const jassigl = {};
|
||||
for (const i in result.users) {
|
||||
jmem = `${jmem + result.users[i].username},`;
|
||||
jmeml[result.users[i]._id] = result.users[i].username;
|
||||
}
|
||||
jmem = jmem.substr(0, jmem.length - 1);
|
||||
for (const ia in result.users) {
|
||||
jassig = `${jassig + result.users[ia].username},`;
|
||||
jassigl[result.users[ia]._id] = result.users[ia].username;
|
||||
}
|
||||
jassig = jassig.substr(0, jassig.length - 1);
|
||||
//get kanban list info
|
||||
const jlist = {};
|
||||
for (const klist in jdata.lists) {
|
||||
jlist[jdata.lists[klist]._id] = jdata.lists[klist].title;
|
||||
for (const klist in result.lists) {
|
||||
jlist[result.lists[klist]._id] = result.lists[klist].title;
|
||||
}
|
||||
//get kanban label info
|
||||
const jlabel = {};
|
||||
for (const klabel in jdata.labels) {
|
||||
jlabel[jdata.labels[klabel]._id] = jdata.labels[klabel].name;
|
||||
for (const klabel in result.labels) {
|
||||
jlabel[result.labels[klabel]._id] = result.labels[klabel].name;
|
||||
}
|
||||
//add data +8 hours
|
||||
function add8hours(jdate) {
|
||||
const curdate = new Date(jdate);
|
||||
return new Date(curdate.setHours(curdate.getHours() + 8));
|
||||
function addTZhours(jdate) {
|
||||
let curdate = new Date(jdate);
|
||||
let checkCorrectDate = moment(curdate);
|
||||
if (checkCorrectDate.isValid()) {
|
||||
return curdate;
|
||||
} else {
|
||||
return ' ';
|
||||
}
|
||||
////Do not add 8 hours to GMT. Use GMT instead.
|
||||
////Could not yet figure out how to get localtime.
|
||||
//return new Date(curdate.setHours(curdate.getHours() + 8));
|
||||
//return curdate;
|
||||
}
|
||||
//add blank row
|
||||
ws.addRow().values = ['', '', '', '', '', '', '', ''];
|
||||
ws.addRow().values = ['', '', '', '', '', ''];
|
||||
//add kanban info
|
||||
ws.addRow().values = [
|
||||
TAPi18n.__('createdAt'),
|
||||
add8hours(jdata.createdAt),
|
||||
addTZhours(result.createdAt),
|
||||
TAPi18n.__('modifiedAt'),
|
||||
add8hours(jdata.modifiedAt),
|
||||
TAPi18n.__('r-member'),
|
||||
addTZhours(result.modifiedAt),
|
||||
TAPi18n.__('members'),
|
||||
jmem,
|
||||
];
|
||||
ws.getRow(3).font = {
|
||||
@@ -328,6 +413,7 @@ export class ExporterExcel {
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
ws.mergeCells('F3:P3');
|
||||
ws.getCell('B3').style = {
|
||||
font: {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
@@ -344,12 +430,19 @@ export class ExporterExcel {
|
||||
wrapText: true,
|
||||
};
|
||||
}
|
||||
function cellLeft(cellno) {
|
||||
ws.getCell(cellno).alignment = {
|
||||
vertical: 'middle',
|
||||
horizontal: 'left',
|
||||
wrapText: true,
|
||||
};
|
||||
}
|
||||
cellCenter('A3');
|
||||
cellCenter('B3');
|
||||
cellCenter('C3');
|
||||
cellCenter('D3');
|
||||
cellCenter('E3');
|
||||
cellCenter('F3');
|
||||
cellLeft('F3');
|
||||
ws.getRow(3).height = 20;
|
||||
//all border
|
||||
function allBorder(cellno) {
|
||||
@@ -375,18 +468,42 @@ export class ExporterExcel {
|
||||
allBorder('E3');
|
||||
allBorder('F3');
|
||||
//add blank row
|
||||
ws.addRow().values = ['', '', '', '', '', '', '', '', ''];
|
||||
ws.addRow().values = [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
];
|
||||
//add card title
|
||||
//ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签'];
|
||||
ws.addRow().values = [
|
||||
TAPi18n.__('number'),
|
||||
TAPi18n.__('title'),
|
||||
TAPi18n.__('description'),
|
||||
TAPi18n.__('owner'),
|
||||
TAPi18n.__('createdAt'),
|
||||
TAPi18n.__('last-modified-at'),
|
||||
TAPi18n.__('card-received'),
|
||||
TAPi18n.__('card-start'),
|
||||
TAPi18n.__('card-due'),
|
||||
TAPi18n.__('card-end'),
|
||||
TAPi18n.__('list'),
|
||||
TAPi18n.__('description'),
|
||||
TAPi18n.__('status'),
|
||||
TAPi18n.__('assignee'),
|
||||
TAPi18n.__('members'),
|
||||
TAPi18n.__('labels'),
|
||||
TAPi18n.__('overtime-hours'),
|
||||
TAPi18n.__('spent-time-hours'),
|
||||
];
|
||||
ws.getRow(5).height = 20;
|
||||
allBorder('A5');
|
||||
@@ -398,6 +515,13 @@ export class ExporterExcel {
|
||||
allBorder('G5');
|
||||
allBorder('H5');
|
||||
allBorder('I5');
|
||||
allBorder('J5');
|
||||
allBorder('K5');
|
||||
allBorder('L5');
|
||||
allBorder('M5');
|
||||
allBorder('N5');
|
||||
allBorder('O5');
|
||||
allBorder('P5');
|
||||
cellCenter('A5');
|
||||
cellCenter('B5');
|
||||
cellCenter('C5');
|
||||
@@ -407,6 +531,13 @@ export class ExporterExcel {
|
||||
cellCenter('G5');
|
||||
cellCenter('H5');
|
||||
cellCenter('I5');
|
||||
cellCenter('J5');
|
||||
cellCenter('K5');
|
||||
cellCenter('L5');
|
||||
cellCenter('M5');
|
||||
cellCenter('N5');
|
||||
cellCenter('O5');
|
||||
cellCenter('P5');
|
||||
ws.getRow(5).font = {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
size: 12,
|
||||
@@ -414,34 +545,46 @@ export class ExporterExcel {
|
||||
};
|
||||
//add blank row
|
||||
//add card info
|
||||
for (const i in jdata.cards) {
|
||||
const jcard = jdata.cards[i];
|
||||
for (const i in result.cards) {
|
||||
const jcard = result.cards[i];
|
||||
//get member info
|
||||
let jcmem = '';
|
||||
for (const j in jcard.members) {
|
||||
jcmem += jmeml[jcard.members[j]];
|
||||
jcmem += ' ';
|
||||
}
|
||||
//get assignee info
|
||||
let jcassig = '';
|
||||
for (const ja in jcard.assignees) {
|
||||
jcassig += jassigl[jcard.assignees[ja]];
|
||||
jcassig += ' ';
|
||||
}
|
||||
//get card label info
|
||||
let jclabel = '';
|
||||
for (const jl in jcard.labelIds) {
|
||||
jclabel += jlabel[jcard.labelIds[jl]];
|
||||
jclabel += ' ';
|
||||
}
|
||||
// console.log(jclabel);
|
||||
|
||||
//add card detail
|
||||
const t = Number(i) + 1;
|
||||
ws.addRow().values = [
|
||||
t.toString(),
|
||||
jcard.title,
|
||||
jmeml[jcard.userId],
|
||||
add8hours(jcard.createdAt),
|
||||
add8hours(jcard.dateLastActivity),
|
||||
jlist[jcard.listId],
|
||||
jcmem,
|
||||
jcard.description,
|
||||
jmeml[jcard.userId],
|
||||
addTZhours(jcard.createdAt),
|
||||
addTZhours(jcard.dateLastActivity),
|
||||
addTZhours(jcard.receivedAt),
|
||||
addTZhours(jcard.startAt),
|
||||
addTZhours(jcard.dueAt),
|
||||
addTZhours(jcard.endAt),
|
||||
jlist[jcard.listId],
|
||||
jcassig,
|
||||
jcmem,
|
||||
jclabel,
|
||||
jcard.isOvertime ? 'true' : 'false',
|
||||
jcard.spentTime,
|
||||
];
|
||||
const y = Number(i) + 6;
|
||||
//ws.getRow(y).height = 25;
|
||||
@@ -454,20 +597,31 @@ export class ExporterExcel {
|
||||
allBorder(`G${y}`);
|
||||
allBorder(`H${y}`);
|
||||
allBorder(`I${y}`);
|
||||
allBorder(`J${y}`);
|
||||
allBorder(`K${y}`);
|
||||
allBorder(`L${y}`);
|
||||
allBorder(`M${y}`);
|
||||
allBorder(`N${y}`);
|
||||
allBorder(`O${y}`);
|
||||
allBorder(`P${y}`);
|
||||
cellCenter(`A${y}`);
|
||||
ws.getCell(`B${y}`).alignment = {
|
||||
wrapText: true,
|
||||
};
|
||||
ws.getCell(`H${y}`).alignment = {
|
||||
ws.getCell(`C${y}`).alignment = {
|
||||
wrapText: true,
|
||||
};
|
||||
ws.getCell(`I${y}`).alignment = {
|
||||
ws.getCell(`L${y}`).alignment = {
|
||||
wrapText: true,
|
||||
};
|
||||
ws.getCell(`M${y}`).alignment = {
|
||||
wrapText: true,
|
||||
};
|
||||
ws.getCell(`N${y}`).alignment = {
|
||||
wrapText: true,
|
||||
};
|
||||
}
|
||||
// var exporte=new Stream;
|
||||
workbook.xlsx.write(res).then(function() {});
|
||||
// return exporte;
|
||||
}
|
||||
|
||||
canExport(user) {
|
||||
|
||||
@@ -243,6 +243,7 @@ export class TrelloCreator {
|
||||
showOnCard: field.display.cardFront,
|
||||
showLabelOnMiniCard: field.display.cardFront,
|
||||
automaticallyOnCard: true,
|
||||
alwaysOnCard: false,
|
||||
type: field.type,
|
||||
boardIds: [boardId],
|
||||
settings: {},
|
||||
|
||||
+4
-2
@@ -1009,8 +1009,10 @@ if (Meteor.isServer) {
|
||||
user.username = user.services.oidc.username;
|
||||
user.emails = [{ address: email, verified: true }];
|
||||
const initials = user.services.oidc.fullname
|
||||
.match(/\b[a-zA-Z]/g)
|
||||
.join('')
|
||||
.split(/\s+/)
|
||||
.reduce((memo, word) => {
|
||||
return memo + word[0];
|
||||
}, '')
|
||||
.toUpperCase();
|
||||
user.profile = {
|
||||
initials,
|
||||
|
||||
@@ -25,6 +25,13 @@ SessionData.attachSchema(
|
||||
type: String,
|
||||
optional: false,
|
||||
},
|
||||
sessionId: {
|
||||
/**
|
||||
* unique session ID
|
||||
*/
|
||||
type: String,
|
||||
optional: false,
|
||||
},
|
||||
totalHits: {
|
||||
/**
|
||||
* total number of hits in the last report query
|
||||
@@ -32,6 +39,13 @@ SessionData.attachSchema(
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
resultsCount: {
|
||||
/**
|
||||
* number of results returned
|
||||
*/
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
lastHit: {
|
||||
/**
|
||||
* the last hit returned from a report query
|
||||
@@ -39,6 +53,48 @@ SessionData.attachSchema(
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
cards: {
|
||||
type: [String],
|
||||
optional: true,
|
||||
},
|
||||
selector: {
|
||||
type: String,
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
errorMessages: {
|
||||
type: [String],
|
||||
optional: true,
|
||||
},
|
||||
errors: {
|
||||
type: [Object],
|
||||
optional: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
'errors.$': {
|
||||
type: new SimpleSchema({
|
||||
tag: {
|
||||
/**
|
||||
* i18n tag
|
||||
*/
|
||||
type: String,
|
||||
optional: false,
|
||||
},
|
||||
value: {
|
||||
/**
|
||||
* value for the tag
|
||||
*/
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
color: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
createdAt: {
|
||||
/**
|
||||
* creation date of the team
|
||||
@@ -70,4 +126,50 @@ SessionData.attachSchema(
|
||||
}),
|
||||
);
|
||||
|
||||
SessionData.helpers({
|
||||
getSelector() {
|
||||
return SessionData.unpickle(this.selector);
|
||||
},
|
||||
});
|
||||
|
||||
SessionData.unpickle = pickle => {
|
||||
return JSON.parse(pickle, (key, value) => {
|
||||
if (typeof value === 'object') {
|
||||
if (value.hasOwnProperty('$$class')) {
|
||||
if (value.$$class === 'RegExp') {
|
||||
return new RegExp(value.source, value.flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
SessionData.pickle = value => {
|
||||
return JSON.stringify(value, (key, value) => {
|
||||
if (typeof value === 'object') {
|
||||
if (value.constructor.name === 'RegExp') {
|
||||
return {
|
||||
$$class: 'RegExp',
|
||||
source: value.source,
|
||||
flags: value.flags,
|
||||
};
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
if (!Meteor.isServer) {
|
||||
SessionData.getSessionId = () => {
|
||||
let sessionId = Session.get('sessionId');
|
||||
if (!sessionId) {
|
||||
sessionId = `${String(Meteor.userId())}-${String(Math.random())}`;
|
||||
Session.set('sessionId', sessionId);
|
||||
}
|
||||
|
||||
return sessionId;
|
||||
};
|
||||
}
|
||||
|
||||
export default SessionData;
|
||||
@@ -537,6 +537,7 @@ export class WekanCreator {
|
||||
showOnCard: field.showOnCard,
|
||||
showLabelOnMiniCard: field.showLabelOnMiniCard,
|
||||
automaticallyOnCard: field.automaticallyOnCard,
|
||||
alwaysOnCard: field.alwaysOnCard,
|
||||
//use date "now" if now created at date is provided (e.g. for very old boards)
|
||||
createdAt: this._now(this.createdAt.customFields[field._id]),
|
||||
modifiedAt: field.modifiedAt,
|
||||
|
||||
Generated
+548
-488
File diff suppressed because it is too large.
Load diff
+16
-16
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wekan",
|
||||
"version": "v4.84.0",
|
||||
"version": "v4.89.0",
|
||||
"description": "Open-Source kanban",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -45,38 +45,38 @@
|
||||
"eslint-config-meteor": "0.0.9",
|
||||
"eslint-config-prettier": "^3.6.0",
|
||||
"eslint-import-resolver-meteor": "^0.4.0",
|
||||
"eslint-plugin-import": "^2.20.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-meteor": "^5.1.0",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"lint-staged": "^7.3.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier-eslint": "^9.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/runtime": "^7.9.6",
|
||||
"@root/request": "^1.6.1",
|
||||
"ajv": "^6.12.4",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@root/request": "^1.7.0",
|
||||
"ajv": "^7.0.3",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bcrypt": "^5.0.0",
|
||||
"bson": "^4.0.3",
|
||||
"bunyan": "^1.8.12",
|
||||
"bson": "^4.2.2",
|
||||
"bunyan": "^1.8.15",
|
||||
"es6-promise": "^4.2.4",
|
||||
"exceljs": "^4.2.0",
|
||||
"fibers": "^5.0.0",
|
||||
"flatted": "^3.0.4",
|
||||
"flatted": "^3.1.1",
|
||||
"gridfs-stream": "https://github.com/wekan/gridfs-stream/tarball/master",
|
||||
"jszip": "^3.4.0",
|
||||
"ldapjs": "^2.1.1",
|
||||
"markdown-it": "^12.0.2",
|
||||
"jszip": "^3.5.0",
|
||||
"ldapjs": "^2.2.3",
|
||||
"markdown-it": "^12.0.4",
|
||||
"markdown-it-emoji": "^2.0.0",
|
||||
"meteor-node-stubs": "^1.0.1",
|
||||
"mongodb": "^3.6.2",
|
||||
"mongodb": "^3.6.3",
|
||||
"os": "^0.1.1",
|
||||
"page": "^1.11.5",
|
||||
"papaparse": "^5.2.0",
|
||||
"qs": "^6.9.4",
|
||||
"papaparse": "^5.3.0",
|
||||
"qs": "^6.9.6",
|
||||
"source-map-support": "^0.5.19",
|
||||
"xss": "^1.0.8"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ function mySafeAttrValue(tag, name, value, cssFilter) {
|
||||
/^aodroplink:/ig.test(value) ||
|
||||
/^onenote:/ig.test(value) ||
|
||||
/^file:/ig.test(value) ||
|
||||
/^abasurl:/ig.test(value) ||
|
||||
/^conisio:/ig.test(value) ||
|
||||
/^mailspring:/ig.test(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class LDAP {
|
||||
idle_timeout : this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
|
||||
encryption : this.constructor.settings_get('LDAP_ENCRYPTION'),
|
||||
ca_cert : this.constructor.settings_get('LDAP_CA_CERT'),
|
||||
reject_unauthorized : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
|
||||
reject_unauthorized : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') !== undefined ? this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') : true,
|
||||
Authentication : this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
|
||||
Authentication_UserDN : this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
|
||||
Authentication_Password : this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
|
||||
@@ -100,7 +100,7 @@ export default class LDAP {
|
||||
|
||||
if (this.options.ca_cert && this.options.ca_cert !== '') {
|
||||
// Split CA cert into array of strings
|
||||
const chainLines = this.constructor.settings_get('LDAP_CA_CERT').split('\n');
|
||||
const chainLines = this.constructor.settings_get('LDAP_CA_CERT').replace(/\\n/g,'\n').split('\n');
|
||||
let cert = [];
|
||||
const ca = [];
|
||||
chainLines.forEach((line) => {
|
||||
|
||||
+11
-2
@@ -1524,7 +1524,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
|
||||
<ul class="toc-list-h1">
|
||||
|
||||
<li>
|
||||
<a href="#wekan-rest-api" class="toc-h1 toc-link" data-title="Wekan REST API v4.84">Wekan REST API v4.84</a>
|
||||
<a href="#wekan-rest-api" class="toc-h1 toc-link" data-title="Wekan REST API v4.89">Wekan REST API v4.89</a>
|
||||
|
||||
</li>
|
||||
|
||||
@@ -1982,7 +1982,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
|
||||
<div class="page-wrapper">
|
||||
<div class="dark-box"></div>
|
||||
<div class="content">
|
||||
<h1 id="wekan-rest-api">Wekan REST API v4.84</h1>
|
||||
<h1 id="wekan-rest-api">Wekan REST API v4.89</h1>
|
||||
<blockquote>
|
||||
<p>Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.</p>
|
||||
</blockquote>
|
||||
@@ -7706,6 +7706,7 @@ System.out.println(response.toString());
|
||||
},
|
||||
<span class="hljs-attr">"showOnCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"automaticallyOnCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"alwaysOnCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"showLabelOnMiniCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"string"</span>,
|
||||
<span class="hljs-attr">"modifiedAt"</span>: <span class="hljs-string">"string"</span>
|
||||
@@ -13992,6 +13993,7 @@ UserSecurity
|
||||
},
|
||||
<span class="hljs-attr">"showOnCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"automaticallyOnCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"alwaysOnCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"showLabelOnMiniCard"</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"string"</span>,
|
||||
<span class="hljs-attr">"modifiedAt"</span>: <span class="hljs-string">"string"</span>
|
||||
@@ -14054,6 +14056,13 @@ UserSecurity
|
||||
<td>should the custom fields automatically be added on cards?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>alwaysOnCard</td>
|
||||
<td>boolean</td>
|
||||
<td>true</td>
|
||||
<td>none</td>
|
||||
<td>should the custom field be automatically added to all cards?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>showLabelOnMiniCard</td>
|
||||
<td>boolean</td>
|
||||
<td>true</td>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
swagger: '2.0'
|
||||
info:
|
||||
title: Wekan REST API
|
||||
version: v4.84
|
||||
version: v4.89
|
||||
description: |
|
||||
The REST API allows you to control and extend Wekan with ease.
|
||||
|
||||
@@ -2392,6 +2392,10 @@ definitions:
|
||||
description: |
|
||||
should the custom fields automatically be added on cards?
|
||||
type: boolean
|
||||
alwaysOnCard:
|
||||
description: |
|
||||
should the custom field be automatically added to all cards?
|
||||
type: boolean
|
||||
showLabelOnMiniCard:
|
||||
description: |
|
||||
should the label of the custom field be shown on minicards?
|
||||
@@ -2408,6 +2412,7 @@ definitions:
|
||||
- settings
|
||||
- showOnCard
|
||||
- automaticallyOnCard
|
||||
- alwaysOnCard
|
||||
- showLabelOnMiniCard
|
||||
- modifiedAt
|
||||
CustomFieldsSettings:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
+28
-1
@@ -26,10 +26,37 @@
|
||||
"src": "/StoreLogo.scale-100.png",
|
||||
"sizes": "50x50",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/maskable_icon.png",
|
||||
"sizes": "474x474",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/monochrome-icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "monochrome"
|
||||
}
|
||||
],
|
||||
"screenshots" : [
|
||||
{
|
||||
"src": "/screenshot1.webp",
|
||||
"sizes": "1280x720",
|
||||
"type": "image/webp"
|
||||
},
|
||||
{
|
||||
"src": "/screenshot2.webp",
|
||||
"sizes": "1280x720",
|
||||
"type": "image/webp"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"start_url": "/sign-in",
|
||||
"display": "standalone"
|
||||
"display": "standalone",
|
||||
"orientation": "any",
|
||||
"categories": ["productivity"],
|
||||
"iarc_rating_id": "70d7c4a4-3e5a-4714-a7dc-fa006613ba96"
|
||||
}
|
||||
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user