Merge branch 'search' of github.com:jrsupplee/wekan into search

This commit is contained in:
John R. Supplee 2021-03-06 10:40:28 +02:00
commit be970e4cea
41 changed files with 633 additions and 8860 deletions

View file

@ -1,5 +1,8 @@
## Issue
Email settings:
- https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
Add these issues to elsewhere:
- SECURITY ISSUES: https://github.com/wekan/wekan/blob/master/SECURITY.md
- UCS: https://github.com/wekan/univention/issues

View file

@ -1,11 +1,40 @@
# Upcoming Wekan release
This release adds the following new features:
- [Added autolinking settings in Admin Panel](https://github.com/wekan/wekan/pull/3633).
Thanks to chrisi51.
- [Add custom field editing to the REST API](https://github.com/wekan/wekan/pull/3593).
Thanks to dudeofawesome.
and fixes the following bugs:
- [Try to fix Snap: Removed fibers multi arch from Snap, because Snap build servers do not build correctly with
it](https://github.com/wekan/wekan/commit/a44ca39eb84508441f0f8bdac852745f417f12e7).
Thanks to xet7.
- [Fix search on labels server error](https://github.com/wekan/wekan/pull/3634).
Thanks to jrsupplee.
- [Fixed Bug: inconsistent use of relative/absolute URLs](https://github.com/wekan/wekan/pull/3635).
Thanks to Majed6.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.03 2021-03-03 Wekan release
This release adds the following changes:
- [Hide email settings from Sandstorm Wekan Admin Panel](https://github.com/wekan/wekan/commit/626f435edf75fac68448ba2e14c62acb749f9c9b).
Thanks to ocdtrekkie and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
and fixes the following bugs:
- [Revert Removed extra imports of Meteor. Hopefully fixes email notifications and rules
on old cars not working](https://github.com/wekan/wekan/commit/e4a9dc25ecc230829afea07dbb3915b96115f7f7).
Thanks to xet7.
- [Fixed Bug: Link at board title can not be edited](https://github.com/wekan/wekan/commit/7d3917adb79be09356d32612585029392bac1e49).
Thanks to jonesrussell42, aiac, bbyszio and xet7.
Thanks to above GitHub and Wekan vanila.io community users for their contributions and translators for their translations.
# v5.02 2021-03-02 Wekan release

View file

@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v5.02.0"
appVersion: "v5.03.0"
files:
userUploads:
- README.md

View file

@ -82,7 +82,7 @@ template(name="activity")
+viewer
= activity.checklist.title
else
a.activity-checklist(href="{{ activity.card.absoluteUrl }}")
a.activity-checklist(href="{{ activity.card.originRelativeUrl }}")
+viewer
= activity.checklist.title
@ -103,7 +103,7 @@ template(name="activity")
if($eq activity.activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' (sanitize activity.checklist.title) cardLink}}}.
.activity-checklist(href="{{ activity.card.absoluteUrl }}")
.activity-checklist(href="{{ activity.card.originRelativeUrl }}")
+viewer
= activity.checklistItem.title
@ -139,7 +139,7 @@ template(name="activity")
//- if we are not in card mode we only display a summary of the comment
if($eq activity.activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ activity.card.absoluteUrl }}")
a.activity-comment(href="{{ activity.card.originRelativeUrl }}")
+viewer
= activity.comment.text

View file

@ -243,7 +243,7 @@ function createCardLink(card) {
Blaze.toHTML(
HTML.A(
{
href: card.absoluteUrl(),
href: card.originRelativeUrl(),
class: 'action-card',
},
sanitizeXss(card.title),
@ -260,7 +260,7 @@ function createBoardLink(board, list) {
Blaze.toHTML(
HTML.A(
{
href: board.absoluteUrl(),
href: board.originRelativeUrl(),
class: 'action-board',
},
sanitizeXss(text),

View file

@ -1,14 +1,17 @@
template(name="boardHeaderBar")
h1.header-board-menu
with currentBoard
a(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}")
+viewer
= title
+viewer
= title
.board-header-btns.left
unless isMiniScreen
if currentBoard
if currentUser
with currentBoard
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
i.fa.fa-pencil-square-o
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
@ -45,6 +48,10 @@ template(name="boardHeaderBar")
if currentBoard
if isMiniScreen
if currentUser
with currentBoard
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
i.fa.fa-pencil-square-o
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")

View file

@ -8,11 +8,11 @@ template(name="cardDetails")
a.fa.fa-times-thin.close-card-details.js-close-card-details
if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
input.inline-input(type="text" id="cardURL_copy" value="{{ absoluteUrl }}")
input.inline-input(type="text" id="cardURL_copy" value="{{ originRelativeUrl }}")
a.fa.fa-link.card-copy-button.js-copy-link(
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
value="{{ absoluteUrl }}"
value="{{ originRelativeUrl }}"
)
if isMiniScreen
a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details
@ -533,7 +533,7 @@ template(name="cardMorePopup")
span {{_ 'link-card'}}
= ' '
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
input.inline-input(type="text" id="cardURL" readonly value="{{ absoluteUrl }}" autofocus="autofocus")
input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus")
button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}}
span.clearfix
br

View file

@ -1,13 +1,17 @@
template(name="resultCard")
.result-card-wrapper
a.minicard-wrapper.card-title(href=absoluteUrl)
a.minicard-wrapper.card-title(href=originRelativeUrl)
+minicard(this)
//= card.title
ul.result-card-context-list
li.result-card-context(title="{{_ 'board'}}")
.result-card-block-wrapper
+viewer
= getBoard.title
if boardId
+viewer
= getBoard.title
else
.broken-cards-null
| NULL
if getBoard.archived
i.fa.fa-archive
li.result-card-context.result-card-context-separator
@ -16,8 +20,12 @@ template(name="resultCard")
= ' '
li.result-card-context(title="{{_ 'swimlane'}}")
.result-card-block-wrapper
+viewer
= getSwimlane.title
if swimlaneId
+viewer
= getSwimlane.title
else
.broken-cards-null
| NULL
if getSwimlane.archived
i.fa.fa-archive
li.result-card-context.result-card-context-separator
@ -26,7 +34,11 @@ template(name="resultCard")
= ' '
li.result-card-context(title="{{_ 'list'}}")
.result-card-block-wrapper
+viewer
= getList.title
if listId
+viewer
= getList.title
else
.broken-cards-null
| NULL
if getList.archived
i.fa.fa-archive

View file

@ -5,7 +5,7 @@ template(name="listBody")
+inlinedForm(autoclose=false position="top")
+addCardForm(listId=_id position="top")
each (cardsWithLimit (idOrNull ../../_id))
a.minicard-wrapper.js-minicard(href=absoluteUrl
a.minicard-wrapper.js-minicard(href=originRelativeUrl
class="{{#if cardIsSelected}}is-selected{{/if}}"
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
if MultiSelection.isActive

View file

@ -3,39 +3,15 @@ template(name="brokenCardsHeaderBar")
| {{_ 'broken-cards'}}
template(name="brokenCards")
.wrapper
.broken-cards-wrapper
each card in brokenCardsList
.broken-cards-card-wrapper
.broken-cards-card-title
= card.title
ul.broken-cards-context-list
li.broken-cards-context(title="{{_ 'board'}}")
if card.boardId
+viewer
= card.getBoard.title
else
.broken-cards-null
| NULL
li.broken-cards-context.broken-cards-context-separator
= ' '
| {{_ 'context-separator'}}
= ' '
li.broken-cards-context(title="{{_ 'swimlane'}}")
if card.swimlaneId
+viewer
= card.getSwimlane.title
else
.broken-cards-null
| NULL
li.broken-cards-context
= ' '
| {{_ 'context-separator'}}
= ' '
li.broken-cards-context(title="{{_ 'list'}}")
if card.listId
+viewer
= card.getList.title
else
.broken-cards-null
| NULL
if currentUser
if searching.get
+spinner
else if hasResults.get
.global-search-results-list-wrapper
if hasQueryErrors.get
div
each msg in errorMessages
span.global-search-error-messages
= msg
else
+resultsPaged(this)

View file

@ -1,3 +1,5 @@
import { CardSearchPagedComponent } from "../../lib/cardSearch";
BlazeComponent.extendComponent({}).register('brokenCardsHeaderBar');
Template.brokenCards.helpers({
@ -6,23 +8,11 @@ Template.brokenCards.helpers({
},
});
BlazeComponent.extendComponent({
class BrokenCardsComponent extends CardSearchPagedComponent {
onCreated() {
Meteor.subscribe('setting');
Meteor.subscribe('brokenCards');
},
super.onCreated();
brokenCardsList() {
const selector = {
$or: [
{ boardId: { $in: [null, ''] } },
{ swimlaneId: { $in: [null, ''] } },
{ listId: { $in: [null, ''] } },
{ permission: 'public' },
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
],
};
return Cards.find(selector);
},
}).register('brokenCards');
Meteor.subscribe('brokenCards', this.sessionId);
}
}
BrokenCardsComponent.register('brokenCards');

View file

@ -57,7 +57,7 @@ class DueCardsComponent extends CardSearchPagedComponent {
queryParams.users = [Meteor.user().username];
}
this.autorunGlobalSearch(queryParams);
this.runGlobalSearch(queryParams);
}
dueCardsView() {

View file

@ -50,6 +50,12 @@ template(name="globalSearch")
= msg
else
+resultsPaged(this)
else if serverError.get
.global-search-page
.global-search-help
h1 {{_ 'server-error' }}
+viewer
| {{_ 'server-error-troubleshooting' }}
else
.global-search-page
.global-search-help

View file

@ -431,7 +431,7 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
return;
}
this.autorunGlobalSearch(params);
this.runGlobalSearch(params);
}
searchInstructions() {
@ -477,57 +477,42 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
predicate_member: TAPi18n.__('predicate-member'),
};
let text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
text += `\n${TAPi18n.__('globalSearch-instructions-description', tags)}`;
text += `\n\n${TAPi18n.__('globalSearch-instructions-operators', tags)}`;
let text = '';
[
'globalSearch-instructions-operator-board',
'globalSearch-instructions-operator-list',
'globalSearch-instructions-operator-swimlane',
'globalSearch-instructions-operator-comment',
'globalSearch-instructions-operator-label',
'globalSearch-instructions-operator-hash',
'globalSearch-instructions-operator-user',
'globalSearch-instructions-operator-at',
'globalSearch-instructions-operator-member',
'globalSearch-instructions-operator-assignee',
'globalSearch-instructions-operator-due',
'globalSearch-instructions-operator-created',
'globalSearch-instructions-operator-modified',
'globalSearch-instructions-operator-status',
].forEach(instruction => {
text += `\n* ${TAPi18n.__(instruction, tags)}`;
});
[
'globalSearch-instructions-status-archived',
'globalSearch-instructions-status-public',
'globalSearch-instructions-status-private',
'globalSearch-instructions-status-all',
'globalSearch-instructions-status-ended',
].forEach(instruction => {
text += `\n * ${TAPi18n.__(instruction, tags)}`;
});
[
'globalSearch-instructions-operator-has',
'globalSearch-instructions-operator-sort',
'globalSearch-instructions-operator-limit',
].forEach(instruction => {
text += `\n* ${TAPi18n.__(instruction, tags)}`;
});
text += `\n## ${TAPi18n.__('heading-notes')}`;
[
'globalSearch-instructions-notes-1',
'globalSearch-instructions-notes-2',
'globalSearch-instructions-notes-3',
'globalSearch-instructions-notes-3-2',
'globalSearch-instructions-notes-4',
'globalSearch-instructions-notes-5',
].forEach(instruction => {
text += `\n* ${TAPi18n.__(instruction, tags)}`;
['# ', 'globalSearch-instructions-heading'],
['\n', 'globalSearch-instructions-description'],
['\n\n', 'globalSearch-instructions-operators'],
['\n* ', 'globalSearch-instructions-operator-board'],
['\n* ', 'globalSearch-instructions-operator-list'],
['\n* ', 'globalSearch-instructions-operator-swimlane'],
['\n* ', 'globalSearch-instructions-operator-comment'],
['\n* ', 'globalSearch-instructions-operator-label'],
['\n* ', 'globalSearch-instructions-operator-hash'],
['\n* ', 'globalSearch-instructions-operator-user'],
['\n* ', 'globalSearch-instructions-operator-at'],
['\n* ', 'globalSearch-instructions-operator-member'],
['\n* ', 'globalSearch-instructions-operator-assignee'],
['\n* ', 'globalSearch-instructions-operator-due'],
['\n* ', 'globalSearch-instructions-operator-created'],
['\n* ', 'globalSearch-instructions-operator-modified'],
['\n* ', 'globalSearch-instructions-operator-status'],
['\n * ', 'globalSearch-instructions-status-archived'],
['\n * ', 'globalSearch-instructions-status-public'],
['\n * ', 'globalSearch-instructions-status-private'],
['\n * ', 'globalSearch-instructions-status-all'],
['\n * ', 'globalSearch-instructions-status-ended'],
['\n* ', 'globalSearch-instructions-operator-has'],
['\n* ', 'globalSearch-instructions-operator-sort'],
['\n* ', 'globalSearch-instructions-operator-limit'],
['\n## ', 'heading-notes'],
['\n* ', 'globalSearch-instructions-notes-1'],
['\n* ', 'globalSearch-instructions-notes-2'],
['\n* ', 'globalSearch-instructions-notes-3'],
['\n* ', 'globalSearch-instructions-notes-3-2'],
['\n* ', 'globalSearch-instructions-notes-4'],
['\n* ', 'globalSearch-instructions-notes-5'],
].forEach(([prefix, instruction]) => {
text += `${prefix}${TAPi18n.__(instruction, tags)}`;
});
return text;

View file

@ -32,7 +32,7 @@ template(name="myCards")
each board in myCardsList
.my-cards-board-wrapper
.my-cards-board-title(class=board.colorClass, id="header")
a(href=board.absoluteUrl)
a(href=board.originRelativeUrl)
+viewer
= board.title
each swimlane in board.mySwimlanes
@ -46,7 +46,7 @@ template(name="myCards")
= list.title
each card in list.myCards
.my-cards-card-wrapper
a.minicard-wrapper(href=card.absoluteUrl)
a.minicard-wrapper(href=card.originRelativeUrl)
+minicard(card)
else
.my-cards-dueat-list-wrapper

View file

@ -53,7 +53,7 @@ class MyCardsComponent extends CardSearchPagedComponent {
sort: { name: 'dueAt', order: 'des' },
};
this.autorunGlobalSearch(queryParams);
this.runGlobalSearch(queryParams);
Meteor.subscribe('setting');
}

Binary file not shown.

View file

@ -211,6 +211,10 @@ template(name='layoutSettings')
.title {{_ 'custom-top-left-corner-logo-height'}}
.form-group
input.wekan-form-control#custom-top-left-corner-logo-height(type="text", placeholder="" value="{{currentSetting.customTopLeftCornerLogoHeight}}")
li.layout-form
.title {{_ 'automatic-linked-url-schemes'}}
.form-group
textarea#automatic-linked-url-schemes.wekan-form-control= currentSetting.automaticLinkedUrlSchemes
li
button.js-save-layout.primary {{_ 'save'}}

View file

@ -176,6 +176,9 @@ BlazeComponent.extendComponent({
const textBelowCustomLoginLogo = $('#text-below-custom-login-logo')
.val()
.trim();
const automaticLinkedUrlSchemes = $('#automatic-linked-url-schemes')
.val()
.trim();
const customTopLeftCornerLogoImageUrl = $(
'#custom-top-left-corner-logo-image-url',
)
@ -209,6 +212,7 @@ BlazeComponent.extendComponent({
customTopLeftCornerLogoHeight,
displayAuthenticationMethod,
defaultAuthenticationMethod,
automaticLinkedUrlSchemes,
},
});
} catch (e) {

View file

@ -4,9 +4,9 @@ template(name="searchSidebar")
.list-body
.minilists.clearfix.js-minilists
each (lists)
a.minilist-wrapper.js-minilist(href=absoluteUrl)
a.minilist-wrapper.js-minilist(href=originRelativeUrl)
+minilist(this)
.minicards.clearfix.js-minicards
each (results)
a.minicard-wrapper.js-minicard(href=absoluteUrl)
a.minicard-wrapper.js-minicard(href=originRelativeUrl)
+minicard(this)

View file

@ -13,6 +13,28 @@ export class CardSearchPagedComponent extends BlazeComponent {
this.totalHits = 0;
this.queryErrors = null;
this.resultsPerPage = 25;
this.sessionId = SessionData.getSessionId();
this.subscriptionHandle = null;
this.serverError = new ReactiveVar(false);
const that = this;
this.subscriptionCallbacks = {
onReady() {
that.getResults();
that.searching.set(false);
that.hasResults.set(true);
that.serverError.set(false);
},
onError(error) {
that.searching.set(false);
that.hasResults.set(false);
that.serverError.set(true);
console.log('Error.reason:', error.reason);
console.log('Error.message:', error.message);
console.log('Error.stack:', error.stack);
}
};
}
resetSearch() {
@ -21,15 +43,15 @@ export class CardSearchPagedComponent extends BlazeComponent {
this.hasResults.set(false);
this.hasQueryErrors.set(false);
this.resultsHeading.set('');
this.serverError.set(false);
this.resultsCount = 0;
this.totalHits = 0;
this.queryErrors = null;
}
getSessionData() {
getSessionData(sessionId) {
return SessionData.findOne({
userId: Meteor.userId(),
sessionId: SessionData.getSessionId(),
sessionId: sessionId ? sessionId : SessionData.getSessionId(),
});
}
@ -45,6 +67,7 @@ export class CardSearchPagedComponent extends BlazeComponent {
const cards = Cards.find({ _id: { $in: sessionData.cards } }, projection);
this.queryErrors = sessionData.errors;
if (this.queryErrors.length) {
// console.log('queryErrors:', this.queryErrorMessages());
this.hasQueryErrors.set(true);
return null;
}
@ -67,25 +90,21 @@ export class CardSearchPagedComponent extends BlazeComponent {
return null;
}
autorunGlobalSearch(params) {
this.searching.set(true);
stopSubscription() {
if (this.subscriptionHandle) {
this.subscriptionHandle.stop();
}
}
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);
}
});
});
});
runGlobalSearch(params) {
this.searching.set(true);
this.stopSubscription();
this.subscriptionHandle = Meteor.subscribe(
'globalSearch',
this.sessionId,
params,
this.subscriptionCallbacks,
);
}
queryErrorMessages() {
@ -103,37 +122,23 @@ export class CardSearchPagedComponent extends BlazeComponent {
}
nextPage() {
const sessionData = this.getSessionData();
this.autorun(() => {
const handle = Meteor.subscribe('nextPage', sessionData.sessionId);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
if (handle.ready()) {
this.getResults();
this.searching.set(false);
this.hasResults.set(true);
}
});
});
});
this.searching.set(true);
this.stopSubscription();
this.subscriptionHandle = Meteor.subscribe(
'nextPage',
this.sessionId,
this.subscriptionCallbacks,
);
}
previousPage() {
const sessionData = this.getSessionData();
this.autorun(() => {
const handle = Meteor.subscribe('previousPage', sessionData.sessionId);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
if (handle.ready()) {
this.getResults();
this.searching.set(false);
this.hasResults.set(true);
}
});
});
});
this.searching.set(true);
this.stopSubscription();
this.subscriptionHandle = Meteor.subscribe(
'previousPage',
this.sessionId,
this.subscriptionCallbacks,
);
}
getResultsHeading() {

View file

@ -1,6 +1,6 @@
{
"accept": "Přijmout",
"act-activity-notify": "Notifikace aktivit",
"act-activity-notify": "Oznámení",
"act-addAttachment": "přidal(a) přílohu __attachment__ na kartu __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__",
"act-deleteAttachment": "smazal(a) přílohu __attachment__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__",
"act-addSubtask": "přidal(a) podúkol __subtask__ na kartu __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__",
@ -322,8 +322,8 @@
"error-user-notAllowSelf": "Nemůžeš pozvat sám sebe",
"error-user-notCreated": "Tento uživatel není vytvořen",
"error-username-taken": "Toto uživatelské jméno již existuje",
"error-orgname-taken": "This organization name is already taken",
"error-teamname-taken": "This team name is already taken",
"error-orgname-taken": "Jméno organizace již existuje",
"error-teamname-taken": "Jméno týmu již existuje",
"error-email-taken": "Tento email byl již použit",
"export-board": "Exportovat tablo",
"export-board-json": "Exportovat tablo do JSON",
@ -908,7 +908,7 @@
"operator-has": "has",
"operator-limit": "limit",
"predicate-archived": "archivováno",
"predicate-open": "open",
"predicate-open": "otevřít",
"predicate-ended": "ukončeno",
"predicate-all": "vše",
"predicate-overdue": "po termínu",
@ -919,21 +919,21 @@
"predicate-due": "do",
"predicate-modified": "modifikováno",
"predicate-created": "vytvořeno",
"predicate-attachment": "attachment",
"predicate-description": "description",
"predicate-attachment": "příloha",
"predicate-description": "popis",
"predicate-checklist": "zaškrtávací seznam",
"predicate-start": "začátek",
"predicate-end": "konec",
"predicate-assignee": "řešitel",
"predicate-member": "člen",
"predicate-public": "public",
"predicate-private": "private",
"predicate-public": "veřejný",
"predicate-private": "soukromý",
"operator-unknown-error": "%s není operátor",
"operator-number-expected": "operátor __operator__ očekával číslo, ale dostal '__value__'",
"operator-sort-invalid": "pořadí '%s' není platné",
"operator-status-invalid": "'%s' není platný stav",
"operator-has-invalid": "%s is not a valid existence check",
"operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.",
"operator-limit-invalid": "není platný limit. Limit musí být pozitivní číslo.",
"next-page": "Následující stránka",
"previous-page": "Předchozí stránka",
"heading-notes": "Poznámky",
@ -965,19 +965,19 @@
"globalSearch-instructions-notes-1": "Lze zadat více operátorů současně.",
"globalSearch-instructions-notes-2": "Podobné operátory jsou spojeny pomocí *OR*. Jsou zobrazeny karty odpovídající kterékoli v podmínek.\n`__operator_list__:Available __operator_list__:Blocked` zobrazí karty obsažené v jakémkoli sloupci s názvem *Blocked* nebo *Available*.",
"globalSearch-instructions-notes-3": "Odlišné operátory jsou spojeny pomocí *AND*. Jsou zobrazeny pouze karty, které odpovídají všem definovaným operátorům. `__operator_list__:Available __operator_label__:red` zobrazí pouze karty ve sloupci *Available* with a štítkem *red*.",
"globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.",
"globalSearch-instructions-notes-3-2": "Dny mohou být specifikovány jako pozitivní nebo negativní číslic, nebo použitím `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.",
"globalSearch-instructions-notes-4": "Vyhledávání rozlišuje velikost písmen.",
"globalSearch-instructions-notes-5": "By default archived cards are not searched.",
"globalSearch-instructions-notes-5": "Ve výchozím zobrazení nejsou vyhledávány archivované položky.",
"link-to-search": "Odkaz na toto vyhledávání",
"excel-font": "Arial",
"number": "Číslo",
"label-colors": "Štítek barvy",
"label-names": "Štítek jména",
"archived-at": "archivováno",
"sort-cards": "Sort Cards",
"cardsSortPopup-title": "Sort Cards",
"due-date": "Due Date",
"title-alphabetically": "Title (Alphabetically)",
"created-at-newest-first": "Created At (Newest First)",
"created-at-oldest-first": "Created At (Oldest First)"
"sort-cards": "Třídit",
"cardsSortPopup-title": "Třídit",
"due-date": "Požadovaný termín",
"title-alphabetically": "Nadpis (Abecedně)",
"created-at-newest-first": "Vyvtořeno (Od nejnovějších)",
"created-at-oldest-first": "Vytvořeno (Od nejstarších)"
}

View file

@ -532,6 +532,7 @@
"custom-login-logo-image-url": "Custom Login Logo Image URL",
"custom-login-logo-link-url": "Custom Login Logo Link URL",
"text-below-custom-login-logo": "Text below Custom Login Logo",
"automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line",
"username": "Username",
"import-usernames": "Import Usernames",
"view-it": "View it",
@ -946,7 +947,7 @@
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*",
"globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.",
"globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name | color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`",
"globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* 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 *<username>* is a *member*",
@ -978,6 +979,8 @@
"sort-cards": "Sort Cards",
"cardsSortPopup-title": "Sort Cards",
"due-date": "Due Date",
"server-error": "Server Error",
"server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation on Linux, run: `sudo journalctl -u 'snap.wekan.*'`",
"title-alphabetically": "Title (Alphabetically)",
"created-at-newest-first": "Created At (Newest First)",
"created-at-oldest-first": "Created At (Oldest First)"

View file

@ -944,8 +944,8 @@
"globalSearch-instructions-operator-list": "`__operator_list__:<titre>` - cartes dont les listes correspondent à *<titre>*",
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<titre>` - cartes dans les couloirs correspondant au *<titre>* spécifié",
"globalSearch-instructions-operator-comment": "`__operator_comment__:<texte>` - cartes dont le commentaire contient *<texte>*.",
"globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name | color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`",
"globalSearch-instructions-operator-label": "`__operator_label__:<couleur>` `__operator_label__:<nom>` - cartes qui ont une étiquette correspondant à *<couleur>* ou à *<nom>*.",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<nom|couleur>` - raccourci pour `__operator_label__:<couleur>` ou `__operator_label__:<nom>`",
"globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* 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 *<username>* is a *member*",

View file

@ -1,3 +1,5 @@
import { Meteor } from 'meteor/meteor';
Actions = new Mongo.Collection('actions');
Actions.allow({

View file

@ -777,6 +777,9 @@ Boards.helpers({
absoluteUrl() {
return FlowRouter.url('board', { id: this._id, slug: this.slug });
},
originRelativeUrl() {
return FlowRouter.path('board', { id: this._id, slug: this.slug });
},
colorClass() {
return `board-color-${this.color}`;

View file

@ -758,6 +758,14 @@ Cards.helpers({
cardId: this._id,
});
},
originRelativeUrl() {
const board = this.board();
return FlowRouter.path('card', {
boardId: board._id,
slug: board.slug,
cardId: this._id,
});
},
canBeRestored() {
const list = Lists.findOne({

View file

@ -370,6 +370,180 @@ if (Meteor.isServer) {
});
});
/**
* @operation edit_custom_field
* @summary Update a Custom Field
*
* @param {string} name the name of the custom field
* @param {string} type the type of the custom field
* @param {string} settings the settings object of the custom field
* @param {boolean} showOnCard should we show the custom field on cards?
* @param {boolean} automaticallyOnCard should the custom fields automatically be added on cards?
* @param {boolean} showLabelOnMiniCard should the label of the custom field be shown on minicards?
* @return_type {_id: string}
*/
JsonRoutes.add(
'PUT',
'/api/boards/:boardId/custom-fields/:customFieldId',
(req, res) => {
Authentication.checkUserId(req.userId);
const paramFieldId = req.params.customFieldId;
if (req.body.hasOwnProperty('name')) {
CustomFields.direct.update(
{ _id: paramFieldId },
{ $set: { name: req.body.name } },
);
}
if (req.body.hasOwnProperty('type')) {
CustomFields.direct.update(
{ _id: paramFieldId },
{ $set: { type: req.body.type } },
);
}
if (req.body.hasOwnProperty('settings')) {
CustomFields.direct.update(
{ _id: paramFieldId },
{ $set: { settings: req.body.settings } },
);
}
if (req.body.hasOwnProperty('showOnCard')) {
CustomFields.direct.update(
{ _id: paramFieldId },
{ $set: { showOnCard: req.body.showOnCard } },
);
}
if (req.body.hasOwnProperty('automaticallyOnCard')) {
CustomFields.direct.update(
{ _id: paramFieldId },
{ $set: { automaticallyOnCard: req.body.automaticallyOnCard } },
);
}
if (req.body.hasOwnProperty('alwaysOnCard')) {
CustomFields.direct.update(
{ _id: paramFieldId },
{ $set: { alwaysOnCard: req.body.alwaysOnCard } },
);
}
if (req.body.hasOwnProperty('showLabelOnMiniCard')) {
CustomFields.direct.update(
{ _id: paramFieldId },
{ $set: { showLabelOnMiniCard: req.body.showLabelOnMiniCard } },
);
}
JsonRoutes.sendResult(res, {
code: 200,
data: { _id: paramFieldId },
});
},
);
/**
* @operation add_custom_field_dropdown_items
* @summary Update a Custom Field's dropdown items
*
* @param {string[]} items names of the custom field
* @return_type {_id: string}
*/
JsonRoutes.add(
'POST',
'/api/boards/:boardId/custom-fields/:customFieldId/dropdown-items',
(req, res) => {
Authentication.checkUserId(req.userId);
if (req.body.hasOwnProperty('items') && Array.isArray(req.body.items)) {
CustomFields.direct.update(
{ _id: req.params.customFieldId },
{
$push: {
'settings.dropdownItems': {
$each: req.body.items
.filter(name => typeof name === 'string')
.map(name => ({
_id: Random.id(6),
name,
})),
},
},
},
);
}
JsonRoutes.sendResult(res, {
code: 200,
data: { _id: req.params.customFieldId },
});
},
);
/**
* @operation edit_custom_field_dropdown_item
* @summary Update a Custom Field's dropdown item
*
* @param {string} name names of the custom field
* @return_type {_id: string}
*/
JsonRoutes.add(
'PUT',
'/api/boards/:boardId/custom-fields/:customFieldId/dropdown-items/:dropdownItemId',
(req, res) => {
Authentication.checkUserId(req.userId);
if (req.body.hasOwnProperty('name')) {
CustomFields.direct.update(
{
_id: req.params.customFieldId,
'settings.dropdownItems._id': req.params.dropdownItemId,
},
{
$set: {
'settings.dropdownItems.$': {
_id: req.params.dropdownItemId,
name: req.body.name,
},
},
},
);
}
JsonRoutes.sendResult(res, {
code: 200,
data: { _id: req.params.customFieldId },
});
},
);
/**
* @operation delete_custom_field_dropdown_item
* @summary Update a Custom Field's dropdown items
*
* @param {string} itemId ID of the dropdown item
* @return_type {_id: string}
*/
JsonRoutes.add(
'DELETE',
'/api/boards/:boardId/custom-fields/:customFieldId/dropdown-items/:dropdownItemId',
(req, res) => {
Authentication.checkUserId(req.userId);
CustomFields.direct.update(
{ _id: req.params.customFieldId },
{
$pull: {
'settings.dropdownItems': { _id: req.params.dropdownItemId },
},
},
);
JsonRoutes.sendResult(res, {
code: 200,
data: { _id: req.params.customFieldId },
});
},
);
/**
* @operation delete_custom_field
* @summary Delete a Custom Fields attached to a board

View file

@ -280,6 +280,10 @@ Lists.helpers({
const card = Cards.findOne({ listId: this._id });
return card && card.absoluteUrl();
},
originRelativeUrl() {
const card = Cards.findOne({ listId: this._id });
return card && card.originRelativeUrl();
},
remove() {
Lists.remove({ _id: this._id });
},

View file

@ -1,3 +1,5 @@
import { Meteor } from 'meteor/meteor';
Rules = new Mongo.Collection('rules');
Rules.attachSchema(

View file

@ -62,6 +62,10 @@ Settings.attachSchema(
type: String,
optional: true,
},
automaticLinkedUrlSchemes: {
type: String,
optional: true,
},
customTopLeftCornerLogoImageUrl: {
type: String,
optional: true,

View file

@ -1,3 +1,5 @@
import { Meteor } from 'meteor/meteor';
Triggers = new Mongo.Collection('triggers');
Triggers.mutations({

8522
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v5.02.0",
"version": "v5.03.0",
"description": "Open-Source kanban",
"private": true,
"scripts": {

View file

@ -6,23 +6,40 @@ var Markdown = require('markdown-it')({
breaks: true,
});
// Static URL Scheme Listing
var urlschemes = [
"aodroplink",
"thunderlink",
"cbthunderlink",
"onenote",
"file",
"abasurl",
"conisio",
"mailspring"
];
// Better would be a field in the admin backend to set this dynamically
// instead of putting all known or wanted url schemes here hard into code
// but i was not able to access those settings
// var urlschemes = currentSetting.automaticLinkedUrlSchemes.split('\n');
// put all url schemes into the linkify configuration to automatically make it clickable
for(var i=0; i<urlschemes.length;i++){
//console.log("adding autolink for "+urlschemes[i]);
Markdown.linkify.add(urlschemes[i]+":",'http:');
}
// Additional safeAttrValue function to allow for other specific protocols
// See https://github.com/leizongmin/js-xss/issues/52#issuecomment-241354114
function mySafeAttrValue(tag, name, value, cssFilter) {
// only when the tag is 'a' and attribute is 'href'
// then use your custom function
if (tag === 'a' && name === 'href') {
// only filter the value if starts with 'cbthunderlink:' or 'aodroplink'
if (/^thunderlink:/ig.test(value) ||
/^cbthunderlink:/ig.test(value) ||
/^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;
}
// only filter the value if starts with an registered url scheme
urlscheme = value.split(/:\/\//);
//console.log("validating "+urlscheme[0]);
if(urlschemes.includes(urlscheme[0])) return value;
else {
// use the default safeAttrValue function to process all non cbthunderlinks
return sanitizeXss.safeAttrValue(tag, name, value, cssFilter);

View file

@ -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 v5.02">Wekan REST API v5.02</a>
<a href="#wekan-rest-api" class="toc-h1 toc-link" data-title="Wekan REST API v5.03">Wekan REST API v5.03</a>
</li>
@ -2047,7 +2047,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 v5.01</h1>
<h1 id="wekan-rest-api">Wekan REST API v5.03</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>

View file

@ -1,7 +1,7 @@
swagger: '2.0'
info:
title: Wekan REST API
version: v5.01
version: v5.03
description: |
The REST API allows you to control and extend Wekan with ease.

View file

@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
appTitle = (defaultText = "Wekan"),
# The name of the app as it is displayed to the user.
appVersion = 502,
appVersion = 503,
# Increment this for every release.
appMarketingVersion = (defaultText = "5.02.0~2021-03-02"),
appMarketingVersion = (defaultText = "5.03.0~2021-03-03"),
# Human-readable presentation of the app version.
minUpgradableAppVersion = 0,

View file

@ -13,7 +13,7 @@ Meteor.publish('myCards', function(sessionId) {
// sort: { name: 'dueAt', order: 'des' },
};
return buildQuery(sessionId, queryParams);
return findCards(sessionId, buildQuery(queryParams));
});
// Meteor.publish('dueCards', function(sessionId, allUsers = false) {
@ -44,85 +44,104 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
// eslint-disable-next-line no-console
// console.log('queryParams:', queryParams);
return buildQuery(sessionId, queryParams);
return findCards(sessionId, buildQuery(queryParams));
});
function buildQuery(sessionId, queryParams) {
const userId = Meteor.userId();
class QueryErrors {
constructor() {
this.notFound = {
boards: [],
swimlanes: [],
lists: [],
labels: [],
users: [],
members: [],
assignees: [],
status: [],
comments: [],
};
const errors = new (class {
constructor() {
this.notFound = {
boards: [],
swimlanes: [],
lists: [],
labels: [],
users: [],
members: [],
assignees: [],
status: [],
comments: [],
};
this.colorMap = Boards.colorMap();
}
this.colorMap = Boards.colorMap();
}
hasErrors() {
for (const value of Object.values(this.notFound)) {
if (value.length) {
return true;
}
hasErrors() {
for (const value of Object.values(this.notFound)) {
if (value.length) {
return true;
}
return false;
}
return false;
}
errorMessages() {
const messages = [];
errorMessages() {
const messages = [];
this.notFound.boards.forEach(board => {
messages.push({ tag: 'board-title-not-found', value: board });
this.notFound.boards.forEach(board => {
messages.push({ tag: 'board-title-not-found', value: board });
});
this.notFound.swimlanes.forEach(swim => {
messages.push({ tag: 'swimlane-title-not-found', value: swim });
});
this.notFound.lists.forEach(list => {
messages.push({ tag: 'list-title-not-found', value: list });
});
this.notFound.comments.forEach(comments => {
comments.forEach(text => {
messages.push({ tag: 'comment-not-found', value: text });
});
this.notFound.swimlanes.forEach(swim => {
messages.push({ tag: 'swimlane-title-not-found', value: swim });
});
this.notFound.lists.forEach(list => {
messages.push({ tag: 'list-title-not-found', value: list });
});
this.notFound.comments.forEach(comments => {
comments.forEach(text => {
messages.push({ tag: 'comment-not-found', value: text });
});
this.notFound.labels.forEach(label => {
if (Boards.labelColors().includes(label)) {
messages.push({
tag: 'label-color-not-found',
value: label,
color: true,
});
});
this.notFound.labels.forEach(label => {
} else {
messages.push({
tag: 'label-not-found',
value: label,
color: Boards.labelColors().includes(label),
color: false,
});
});
this.notFound.users.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
this.notFound.members.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
this.notFound.assignees.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
}
});
this.notFound.users.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
this.notFound.members.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
this.notFound.assignees.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
return messages;
return messages;
}
};
class Query {
params = {};
selector = {};
projection = {};
errors = new QueryErrors();
constructor(selector, projection) {
if (selector) {
this.selector = selector;
}
})();
if (projection) {
this.projection = projection;
}
}
}
function buildSelector(queryParams) {
const userId = Meteor.userId();
errors = new QueryErrors();
let selector = {};
let skip = 0;
if (queryParams.skip) {
skip = queryParams.skip;
}
let limit = 25;
if (queryParams.limit) {
limit = queryParams.limit;
}
if (queryParams.selector) {
selector = queryParams.selector;
@ -365,6 +384,9 @@ function buildQuery(sessionId, queryParams) {
boards.forEach(board => {
board.labels
.filter(boardLabel => {
if (!boardLabel.name) {
return false;
}
return boardLabel.name.match(reLabel);
})
.forEach(boardLabel => {
@ -476,6 +498,25 @@ function buildQuery(sessionId, queryParams) {
// eslint-disable-next-line no-console
// console.log('selector.$and:', selector.$and);
const query = new Query();
query.selector = selector;
query.params = queryParams;
query.errors = errors;
return query;
}
function buildProjection(query) {
let skip = 0;
if (query.params.skip) {
skip = query.params.skip;
}
let limit = 25;
if (query.params.limit) {
limit = query.params.limit;
}
const projection = {
fields: {
_id: 1,
@ -505,9 +546,9 @@ function buildQuery(sessionId, queryParams) {
limit,
};
if (queryParams.sort) {
const order = queryParams.sort.order === 'asc' ? 1 : -1;
switch (queryParams.sort.name) {
if (query.params.sort) {
const order = query.params.sort.order === 'asc' ? 1 : -1;
switch (query.params.sort.name) {
case 'dueAt':
projection.sort = {
dueAt: order,
@ -550,77 +591,33 @@ function buildQuery(sessionId, queryParams) {
// eslint-disable-next-line no-console
// console.log('projection:', projection);
return findCards(sessionId, selector, projection, errors);
query.projection = projection;
return query;
}
Meteor.publish('brokenCards', function() {
const user = Users.findOne({ _id: this.userId });
function buildQuery(queryParams) {
const query = buildSelector(queryParams);
const permiitedBoards = [null];
let selector = {};
selector.$or = [
{ permission: 'public' },
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
];
return buildProjection(query);
}
Boards.find(selector).forEach(board => {
permiitedBoards.push(board._id);
});
Meteor.publish('brokenCards', function(sessionId) {
selector = {
boardId: { $in: permiitedBoards },
$or: [
{ boardId: { $in: [null, ''] } },
{ swimlaneId: { $in: [null, ''] } },
{ listId: { $in: [null, ''] } },
],
const queryParams = {
users: [Meteor.user().username],
// limit: 25,
skip: 0,
// sort: { name: 'dueAt', order: 'des' },
};
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,
},
});
const boards = [];
const swimlanes = [];
const lists = [];
const users = [];
cards.forEach(card => {
if (card.boardId) boards.push(card.boardId);
if (card.swimlaneId) swimlanes.push(card.swimlaneId);
if (card.listId) lists.push(card.listId);
if (card.members) {
card.members.forEach(userId => {
users.push(userId);
});
}
if (card.assignees) {
card.assignees.forEach(userId => {
users.push(userId);
});
}
});
return [
cards,
Boards.find({ _id: { $in: boards } }),
Swimlanes.find({ _id: { $in: swimlanes } }),
Lists.find({ _id: { $in: lists } }),
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
const query = buildQuery(queryParams);
query.selector.$or = [
{ boardId: { $in: [null, ''] } },
{ swimlaneId: { $in: [null, ''] } },
{ listId: { $in: [null, ''] } },
];
return findCards(sessionId, query);
});
Meteor.publish('nextPage', function(sessionId) {
@ -630,7 +627,7 @@ Meteor.publish('nextPage', function(sessionId) {
const projection = session.getProjection();
projection.skip = session.lastHit;
return findCards(sessionId, session.getSelector(), projection);
return findCards(sessionId, new Query(session.getSelector(), projection));
});
Meteor.publish('previousPage', function(sessionId) {
@ -640,20 +637,20 @@ Meteor.publish('previousPage', function(sessionId) {
const projection = session.getProjection();
projection.skip = session.lastHit - session.resultsCount - projection.limit;
return findCards(sessionId, session.getSelector(), projection);
return findCards(sessionId, new Query(session.getSelector(), projection));
});
function findCards(sessionId, selector, projection, errors = null) {
function findCards(sessionId, query) {
const userId = Meteor.userId();
// eslint-disable-next-line no-console
console.log('selector:', selector);
console.log('selector.$and:', selector.$and);
console.log('selector:', query.selector);
console.log('selector.$and:', query.selector.$and);
// eslint-disable-next-line no-console
// console.log('projection:', projection);
let cards;
if (!errors || !errors.hasErrors()) {
cards = Cards.find(selector, projection);
cards = Cards.find(query.selector, query.projection);
}
// eslint-disable-next-line no-console
// console.log('count:', cards.count());
@ -664,19 +661,20 @@ function findCards(sessionId, selector, projection, errors = null) {
lastHit: 0,
resultsCount: 0,
cards: [],
selector: SessionData.pickle(selector),
projection: SessionData.pickle(projection),
selector: SessionData.pickle(query.selector),
projection: SessionData.pickle(query.projection),
errors: query.errors.errorMessages(),
},
};
if (errors) {
update.$set.errors = errors.errorMessages();
}
// if (errors) {
// update.$set.errors = errors.errorMessages();
// }
if (cards) {
update.$set.totalHits = cards.count();
update.$set.lastHit =
projection.skip + projection.limit < cards.count()
? projection.skip + projection.limit
query.projection.skip + query.projection.limit < cards.count()
? query.projection.skip + query.projection.limit
: cards.count();
update.$set.cards = cards.map(card => {
return card._id;

View file

@ -15,6 +15,7 @@ Meteor.publish('setting', () => {
customLoginLogoImageUrl: 1,
customLoginLogoLinkUrl: 1,
textBelowCustomLoginLogo: 1,
automaticLinkedUrlSchemes: 1,
customTopLeftCornerLogoImageUrl: 1,
customTopLeftCornerLogoLinkUrl: 1,
customTopLeftCornerLogoHeight: 1,

View file

@ -97,7 +97,7 @@ parts:
- execstack
- nodejs
- npm
- p7zip-full
# - p7zip-full
stage-packages:
- libfontconfig1
override-build: |
@ -128,11 +128,11 @@ parts:
find . -name '*.swp' | xargs rm -f
cd ../..
# Add fibers multi arch
cd .build/bundle/programs/server/node_modules/fibers/bin
curl https://releases.wekan.team/fibers-multi.7z -o fibers-multi.7z
7z x fibers-multi.7z
rm fibers-multi.7z
cd ../../../../../../..
#cd .build/bundle/programs/server/node_modules/fibers/bin
#curl https://releases.wekan.team/fibers-multi.7z -o fibers-multi.7z
#7z x fibers-multi.7z
#rm fibers-multi.7z
#cd ../../../../../../..
# Copy to Snap
cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/