Kaynağa Gözat

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

John R. Supplee 4 yıl önce
ebeveyn
işleme
be970e4cea
41 değiştirilmiş dosya ile 607 ekleme ve 8834 silme
  1. 3 0
      .github/ISSUE_TEMPLATE.md
  2. 30 1
      CHANGELOG.md
  3. 1 1
      Stackerfile.yml
  4. 3 3
      client/components/activities/activities.jade
  5. 2 2
      client/components/activities/activities.js
  6. 10 3
      client/components/boards/boardHeader.jade
  7. 3 3
      client/components/cards/cardDetails.jade
  8. 19 7
      client/components/cards/resultCard.jade
  9. 1 1
      client/components/lists/listBody.jade
  10. 12 36
      client/components/main/brokenCards.jade
  11. 8 18
      client/components/main/brokenCards.js
  12. 1 1
      client/components/main/dueCards.js
  13. 6 0
      client/components/main/globalSearch.jade
  14. 36 51
      client/components/main/globalSearch.js
  15. 2 2
      client/components/main/myCards.jade
  16. 1 1
      client/components/main/myCards.js
  17. BIN
      client/components/rules/.DS_Store
  18. 4 0
      client/components/settings/settingBody.jade
  19. 4 0
      client/components/settings/settingBody.js
  20. 2 2
      client/components/sidebar/sidebarSearches.jade
  21. 54 49
      client/lib/cardSearch.js
  22. 17 17
      i18n/cs.i18n.json
  23. 4 1
      i18n/en.i18n.json
  24. 2 2
      i18n/fr.i18n.json
  25. 2 0
      models/actions.js
  26. 3 0
      models/boards.js
  27. 8 0
      models/cards.js
  28. 174 0
      models/customFields.js
  29. 4 0
      models/lists.js
  30. 2 0
      models/rules.js
  31. 4 0
      models/settings.js
  32. 2 0
      models/triggers.js
  33. 2 8468
      package-lock.json
  34. 1 1
      package.json
  35. 28 11
      packages/markdown/src/template-integration.js
  36. 2 2
      public/api/wekan.html
  37. 1 1
      public/api/wekan.yml
  38. 2 2
      sandstorm-pkgdef.capnp
  39. 140 142
      server/publications/cards.js
  40. 1 0
      server/publications/settings.js
  41. 6 6
      snapcraft.yaml

+ 3 - 0
.github/ISSUE_TEMPLATE.md

@@ -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

+ 30 - 1
CHANGELOG.md

@@ -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
 

+ 1 - 1
Stackerfile.yml

@@ -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

+ 3 - 3
client/components/activities/activities.jade

@@ -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
 

+ 2 - 2
client/components/activities/activities.js

@@ -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),

+ 10 - 3
client/components/boards/boardHeader.jade

@@ -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}}")

+ 3 - 3
client/components/cards/cardDetails.jade

@@ -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

+ 19 - 7
client/components/cards/resultCard.jade

@@ -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

+ 1 - 1
client/components/lists/listBody.jade

@@ -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

+ 12 - 36
client/components/main/brokenCards.jade

@@ -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)

+ 8 - 18
client/components/main/brokenCards.js

@@ -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');

+ 1 - 1
client/components/main/dueCards.js

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

+ 6 - 0
client/components/main/globalSearch.jade

@@ -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

+ 36 - 51
client/components/main/globalSearch.js

@@ -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)}`;
-
-    [
-      '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')}`;
+    let text = '';
     [
-      '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;

+ 2 - 2
client/components/main/myCards.jade

@@ -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

+ 1 - 1
client/components/main/myCards.js

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

BIN
client/components/rules/.DS_Store


+ 4 - 0
client/components/settings/settingBody.jade

@@ -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'}}
 

+ 4 - 0
client/components/settings/settingBody.js

@@ -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) {

+ 2 - 2
client/components/sidebar/sidebarSearches.jade

@@ -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)

+ 54 - 49
client/lib/cardSearch.js

@@ -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() {

+ 17 - 17
i18n/cs.i18n.json

@@ -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)"
 }

+ 4 - 1
i18n/en.i18n.json

@@ -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)"

+ 2 - 2
i18n/fr.i18n.json

@@ -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*",

+ 2 - 0
models/actions.js

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

+ 3 - 0
models/boards.js

@@ -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}`;

+ 8 - 0
models/cards.js

@@ -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({

+ 174 - 0
models/customFields.js

@@ -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

+ 4 - 0
models/lists.js

@@ -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 });
   },

+ 2 - 0
models/rules.js

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

+ 4 - 0
models/settings.js

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

+ 2 - 0
models/triggers.js

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

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 8468
package-lock.json


+ 1 - 1
package.json

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

+ 28 - 11
packages/markdown/src/template-integration.js

@@ -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);

+ 2 - 2
public/api/wekan.html

@@ -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>

+ 1 - 1
public/api/wekan.yml

@@ -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.
 

+ 2 - 2
sandstorm-pkgdef.capnp

@@ -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,

+ 140 - 142
server/publications/cards.js

@@ -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();
-
-  const errors = new (class {
-    constructor() {
-      this.notFound = {
-        boards: [],
-        swimlanes: [],
-        lists: [],
-        labels: [],
-        users: [],
-        members: [],
-        assignees: [],
-        status: [],
-        comments: [],
-      };
+class QueryErrors {
+  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.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.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.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;
     }
-  })();
 
-  let selector = {};
-  let skip = 0;
-  if (queryParams.skip) {
-    skip = queryParams.skip;
-  }
-  let limit = 25;
-  if (queryParams.limit) {
-    limit = queryParams.limit;
+    if (projection) {
+      this.projection = projection;
+    }
   }
+}
+
+function buildSelector(queryParams) {
+  const userId = Meteor.userId();
+
+  errors = new QueryErrors();
+
+  let selector = {};
 
   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;

+ 1 - 0
server/publications/settings.js

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

+ 6 - 6
snapcraft.yaml

@@ -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/

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor