Browse Source

Number of cards per list and sum of custom number field in list head.

Thanks to xet7 !

Fixes #3796
Lauri Ojansivu 15 hours ago
parent
commit
e569c2957e
29 changed files with 239 additions and 142 deletions
  1. 5 8
      client/components/activities/comments.css
  2. 3 3
      client/components/activities/comments.jade
  3. 3 3
      client/components/boards/boardArchive.jade
  4. 1 1
      client/components/boards/miniboard.jade
  5. 2 2
      client/components/cards/cardDetails.css
  6. 6 6
      client/components/cards/cardDetails.jade
  7. 10 10
      client/components/cards/checklists.css
  8. 2 2
      client/components/cards/checklists.jade
  9. 6 2
      client/components/cards/labels.css
  10. 1 1
      client/components/cards/labels.jade
  11. 4 19
      client/components/cards/minicard.css
  12. 1 1
      client/components/cards/minicard.jade
  13. 3 5
      client/components/common/originalPosition.html
  14. 2 2
      client/components/import/import.jade
  15. 18 0
      client/components/lists/list.css
  16. 58 3
      client/components/lists/listBody.js
  17. 6 0
      client/components/lists/listHeader.jade
  18. 42 1
      client/components/lists/listHeader.js
  19. 1 1
      client/components/lists/minilist.jade
  20. 3 3
      client/components/main/layouts.css
  21. 4 4
      client/components/notifications/notification.css
  22. 1 1
      client/components/notifications/notification.jade
  23. 14 14
      client/components/notifications/notificationIcon.jade
  24. 0 3
      client/components/notifications/notificationsDrawer.css
  25. 2 2
      client/components/notifications/notificationsDrawer.jade
  26. 11 11
      client/components/sidebar/sidebarFilters.jade
  27. 2 11
      client/components/swimlanes/swimlaneHeader.jade
  28. 24 19
      client/components/swimlanes/swimlanes.css
  29. 4 4
      client/components/users/userAvatar.css

+ 5 - 8
client/components/activities/comments.css

@@ -108,15 +108,12 @@
   text-decoration: none;
   height: 24px;
 }
-.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
-  font-size: 17px;
+.comments .comment .comment-desc .reactions .open-comment-reaction-popup span {
+  display: inline-block;
+  font-size: clamp(14px, 2vw, 18px);
   font-weight: 500;
-  margin-left: 2px;
-}
-.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
-  font-size: 8px;
-  margin-top: -7px;
-  margin-left: 1px;
+  line-height: 1;
+  margin-left: 4px;
 }
 .comments .comment .comment-desc .reactions .reaction {
   cursor: pointer;

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

@@ -25,7 +25,7 @@ template(name="comment")
           = text
         .edit-controls
           button.primary(type="submit") {{_ 'edit'}}
-          .fa.fa-times-thin.js-close-inlined-form
+          a.js-close-inlined-form(title="{{_ 'close' }}") ❌
       else
         .comment-text
           +viewer
@@ -55,8 +55,8 @@ template(name="commentReactions")
         span.reaction-count #{reaction.userIds.length}
     if (currentUser.isBoardMember)
       a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
-        i.fa.fa-smile-o
-        i.fa.fa-plus
+        span(title="{{_ 'reaction' }}") 😀
+        span(title="{{_ 'add' }}") ➕
 
 template(name="addReactionPopup")
   .reactions-popup

+ 3 - 3
client/components/boards/boardArchive.jade

@@ -1,6 +1,6 @@
 template(name="archivedBoards")
   h2
-    i.fa.fa-archive
+    span(title="{{_ 'archived-boards'}}") 📦
     | {{_ 'archived-boards'}}
 
   ul.archived-lists
@@ -8,10 +8,10 @@ template(name="archivedBoards")
       li.archived-lists-item
         div.board-header-btns
           button.board-header-btn.js-delete-board
-            i.fa.fa-trash-o
+            | 🗑️
             | {{_ 'delete-board'}}
           button.board-header-btn.js-restore-board
-            i.fa.fa-undo
+            | ↩️
             | {{_ 'restore-board'}}
           = title
         span {{ moment archivedAt 'LLL' }}

+ 1 - 1
client/components/boards/miniboard.jade

@@ -3,6 +3,6 @@ template(name="miniboard")
     class="minicard-{{colorClass}}")
     .minicard-title
       .handle
-        .fa.fa-arrows
+        span.drag-handle(title="{{_ 'dragBoard'}}") ↕️
       +viewer
         = title

+ 2 - 2
client/components/cards/cardDetails.css

@@ -31,8 +31,8 @@
   display: block;
   position: relative;
   float: left;
-  height: 30px;
-  width: 30px;
+  height: clamp(24px, 3.5vw, 36px);
+  width: clamp(24px, 3.5vw, 36px);
   margin: .3vh;
   cursor: pointer;
   user-select: none;

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

@@ -26,10 +26,10 @@ template(name="cardDetails")
               | ☰
             a.card-copy-button.js-copy-link(
               id="cardURL_copy"
-              class="fa-link"
               title="{{_ 'copy-card-link-to-clipboard'}}"
               href="{{ originRelativeUrl }}"
             )
+              | 🔗
             span.copied-tooltip {{_ 'copied'}}
         else
           unless isPopup
@@ -40,10 +40,10 @@ template(name="cardDetails")
               | ☰
             a.card-copy-mobile-button.js-copy-link(
               id="cardURL_copy"
-              class="fa-link"
               title="{{_ 'copy-card-link-to-clipboard'}}"
               href="{{ originRelativeUrl }}"
             )
+              | 🔗
             span.copied-tooltip {{_ 'copied'}}
         h2.card-details-title.js-card-title(
           class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
@@ -304,7 +304,7 @@ template(name="cardDetails")
               hr
             .card-details-item.card-details-item-customfield
               h3.card-details-item-title
-                | 📋-alt
+                | 📋
                 = definition.name
               +cardCustomField
 
@@ -678,7 +678,7 @@ template(name="cardDetailsActionsPopup")
           | 👁️
           |  {{_ 'unwatch'}}
         else
-          | 👁️-slash
+          | 👁️
           |  {{_ 'watch'}}
   hr
   if canModifyCard
@@ -698,7 +698,7 @@ template(name="cardDetailsActionsPopup")
         if currentUser.isBoardAdmin
           li
             a.js-custom-fields
-              | 📋-alt
+              | 📋
               | {{_ 'card-edit-custom-fields'}}
         //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
         //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
@@ -718,7 +718,7 @@ template(name="cardDetailsActionsPopup")
               | 👁️
               | {{_ 'hide-list-on-minicard'}}
             else
-              | 👁️-slash
+              | 👁️
               | {{_ 'show-list-on-minicard'}}
   hr
   ul.pop-over-list

+ 10 - 10
client/components/cards/checklists.css

@@ -67,14 +67,14 @@ textarea.js-edit-checklist-item {
 .checklist-title .checklist-stat.is-finished {
   color: #3cb500;
 }
-.checklist-title span.fa.checklist-handle {
+.checklist-title span.checklist-handle {
   padding-right: 20px;
   padding-top: 3px;
   float: left;
-}
-.checklist-title span.fa.checklist-handle.fa-arrows::before {
-  content: "↕️" !important;
-  font-family: inherit !important;
+  display: inline-block;
+  width: 1.2em;
+  text-align: center;
+  color: #999;
 }
 #card-details-overlay {
   top: 0;
@@ -148,13 +148,13 @@ textarea.js-edit-checklist-item {
   word-wrap: break-word;
   max-width: 420px;
 }
-.checklist-item span.fa.checklistitem-handle {
+.checklist-item span.checklistitem-handle {
   padding-top: 2px;
   padding-right: 10px;
-}
-.checklist-item span.fa.checklistitem-handle.fa-arrows::before {
-  content: "↕️" !important;
-  font-family: inherit !important;
+  display: inline-block;
+  width: 1.2em;
+  text-align: center;
+  color: #999;
 }
 .js-delete-checklist-item,
 .js-convert-checklist-item-to-card {

+ 2 - 2
client/components/cards/checklists.jade

@@ -43,7 +43,7 @@ template(name="checklistDetail")
         if canModifyCard
           h4.title.js-open-inlined-form.is-editable
             if isTouchScreenOrShowDesktopDragHandles
-              span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
+              span.checklist-handle(title="{{_ 'dragChecklist'}}") ↕️
             +viewer
               = checklist.title
         else
@@ -127,7 +127,7 @@ template(name='checklistItemDetail')
     if canModifyCard
       .check-box-container
         .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
-      span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
+      span.checklistitem-handle(title="{{_ 'dragChecklistItem'}}") ↕️
       .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
         +viewer
           = item.title

+ 6 - 2
client/components/cards/labels.css

@@ -223,9 +223,13 @@
 .card-label-edit-button:hover {
   background: #dbdbdb;
 }
-ul.edit-labels-pop-over span.fa.label-handle {
+ul.edit-labels-pop-over span.label-handle {
   padding-right: 10px;
+  display: inline-block;
+  width: 1.2em;
+  text-align: center;
+  color: #999;
 }
-ul.edit-labels-pop-over span.fa.label-handle + .card-label {
+ul.edit-labels-pop-over span.label-handle + .card-label {
   max-width: 180px;
 }

+ 1 - 1
client/components/cards/labels.jade

@@ -31,7 +31,7 @@ template(name="cardLabelsPopup")
         a.card-label-edit-button.js-edit-label
           | ✏️
         if isTouchScreenOrShowDesktopDragHandles
-          span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
+          span.label-handle(title="{{_ 'dragLabel'}}") ↕️
         span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
           class="{{# if isLabelSelected ../_id }}active{{/if}}")
             +viewer

+ 4 - 19
client/components/cards/minicard.css

@@ -142,9 +142,12 @@
     display: block;
   }
 }
-.minicard .handle .fa-arrows {
+.minicard .handle .drag-handle {
   font-size: clamp(16px, 3vw, 20px);
   color: #ccc;
+  display: inline-block;
+  width: 1.4em;
+  text-align: center;
 }
 .minicard .minicard-title .card-number {
   color: #b3b3b3;
@@ -297,19 +300,6 @@
   background-color: #1976d2 !important;
 }
 
-/* Font Awesome icons in minicard dates */
-.minicard .card-date i.fa {
-  margin-right: 0.3vw;
-  font-size: 0.9em;
-  vertical-align: middle;
-}
-
-/* Font Awesome icons in minicard spent time */
-.minicard .card-time i.fa {
-  margin-right: 0.3vw;
-  font-size: 0.9em;
-  vertical-align: middle;
-}
 .minicard .badges {
   float: left;
   margin-top: 1vh;
@@ -740,8 +730,3 @@
   align-items: center;
   gap: 0.3vw;
 }
-
-.minicard-list-name i.fa {
-  font-size: 0.8em;
-  opacity: 0.7;
-}

+ 1 - 1
client/components/cards/minicard.jade

@@ -251,6 +251,6 @@ template(name="minicardDetailsActionsPopup")
           | 👁️
           |  {{_ 'unwatch'}}
         else
-          | 👁️-slash
+          | 👁️
           |  {{_ 'watch'}}
 

+ 3 - 5
client/components/common/originalPosition.html

@@ -2,19 +2,17 @@
   <div class="original-position-info">
     {{#if isLoading}}
       <div class="original-position-loading">
-        <i class="fa fa-spinner fa-spin"></i> Loading original position...
+         Loading original position...
       </div>
     {{else if showOriginalPosition}}
       <div class="original-position-details">
         {{#if hasMovedFromOriginal}}
           <div class="original-position-moved">
-            <i class="fa fa-info-circle"></i>
-            <span class="original-position-text">{{getOriginalPositionDescription}}</span>
+            <span class="original-position-text">ℹ️ {{getOriginalPositionDescription}}</span>
           </div>
         {{else}}
           <div class="original-position-unchanged">
-            <i class="fa fa-check-circle"></i>
-            <span class="original-position-text">In original position</span>
+            <span class="original-position-text">✅ In original position</span>
           </div>
         {{/if}}
         

+ 2 - 2
client/components/import/import.jade

@@ -1,7 +1,7 @@
 template(name="importHeaderBar")
   h1
     a.back-btn(href="{{pathFor 'home'}}")
-      i.fa.fa-chevron-left
+      | ⬅️
     | {{_ title}}
 
 template(name="import")
@@ -36,7 +36,7 @@ template(name="importMapMembers")
                 +userAvatar(userId=wekanId)
               else
                 a.member.add-member
-                  i.fa.fa-plus
+                  | ➕
         //-
           Due to the way the flewbox layout is working, we need to set some
           invisible items so that the last row items have a consistent width.

+ 18 - 0
client/components/lists/list.css

@@ -368,6 +368,18 @@ body.list-resizing-active * {
   text-overflow: ellipsis;
   word-wrap: break-word;
 }
+/* Sum badge shown before list title */
+.list-header .list-sum-badge {
+  display: inline-block;
+  margin-right: 8px;
+  padding: 0;
+  border-radius: 0;
+  background: transparent;
+  color: #8c8c8c;
+  font-weight: bold;
+  font-size: 12px;
+  vertical-align: middle;
+}
 .list-rotated {
   width: 1.3vw;
   height: 35vh;
@@ -750,6 +762,9 @@ body.list-resizing-active * {
   grid-row: 2;
   grid-column: 2;
   align-self: start;
+  text-align: left;
+  padding-left: 0;
+  margin-left: 0;
   font-size: 16px !important;
   line-height: 1.2;
 }
@@ -964,6 +979,9 @@ body.list-resizing-active * {
     grid-row: 2 !important;
     grid-column: 2 !important;
     align-self: start !important;
+    text-align: left !important;
+    padding-left: 0 !important;
+    margin-left: 0 !important;
     font-size: 16px !important;
     line-height: 1.2 !important;
   }

+ 58 - 3
client/components/lists/listBody.js

@@ -16,11 +16,50 @@ BlazeComponent.extendComponent({
   },
 
   customFieldsSum() {
-    const ret = ReactiveCache.getCustomFields({
-      boardIds: { $in: [Session.get('currentBoard')] },
+    const list = Template.currentData();
+    if (!list) return [];
+    const boardId = Session.get('currentBoard');
+    const fields = ReactiveCache.getCustomFields({
+      boardIds: { $in: [boardId] },
       showSumAtTopOfList: true,
     });
-    return ret;
+
+    if (!fields || !fields.length) return [];
+
+    const cards = ReactiveCache.getCards({
+      listId: list._id,
+      archived: false,
+    });
+
+    const result = fields.map(field => {
+      let sum = 0;
+      if (cards && cards.length) {
+        cards.forEach(card => {
+          const cfs = (card.customFields || []);
+          const cf = cfs.find(f => f && f._id === field._id);
+          if (!cf || cf.value === null || cf.value === undefined) return;
+          let v = cf.value;
+          if (typeof v === 'string') {
+            // try to parse string numbers, accept comma decimal
+            const parsed = parseFloat(v.replace(',', '.'));
+            if (isNaN(parsed)) return;
+            v = parsed;
+          }
+          if (typeof v === 'number' && isFinite(v)) {
+            sum += v;
+          }
+        });
+      }
+      return {
+        _id: field._id,
+        name: field.name,
+        type: field.type,
+        settings: field.settings || {},
+        value: sum,
+      };
+    });
+
+    return result;
   },
 
   openForm(options) {
@@ -254,6 +293,22 @@ BlazeComponent.extendComponent({
   },
 }).register('listBody');
 
+// Helpers for listBody template context
+Template.listBody.helpers({
+  formattedCurrencyCustomFieldValue(val) {
+    // `this` is the custom field sum object from customFieldsSum each-iteration
+    const field = this || {};
+    const code = (field.settings && field.settings.currencyCode) || 'USD';
+    try {
+      const n = typeof val === 'number' ? val : parseFloat(val);
+      if (!isFinite(n)) return val;
+      return new Intl.NumberFormat(undefined, { style: 'currency', currency: code }).format(n);
+    } catch (e) {
+      return `${code} ${val}`;
+    }
+  },
+});
+
 function toggleValueInReactiveArray(reactiveValue, value) {
   const array = reactiveValue.get();
   const valueIndex = array.indexOf(value);

+ 6 - 0
client/components/lists/listHeader.jade

@@ -26,6 +26,9 @@ template(name="listHeader")
            |/#{wipLimit.value})
         if showCardsCountForList cards.length
           span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
+          if hasNumberFieldsSum
+            | &nbsp;
+            span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
       else
         if collapsed
           a.js-collapse(title="{{_ 'uncollapse'}}")
@@ -44,6 +47,9 @@ template(name="listHeader")
         unless collapsed
           if showCardsCountForList cards.length
             span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
+            if hasNumberFieldsSum
+              | &nbsp;
+              span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
       if isMiniScreen
         if currentList
           if isWatching

+ 42 - 1
client/components/lists/listHeader.js

@@ -142,7 +142,48 @@ BlazeComponent.extendComponent({
 Template.listHeader.helpers({
   isBoardAdmin() {
     return ReactiveCache.getCurrentUser().isBoardAdmin();
-  }
+  },
+  numberFieldsSum() {
+    const list = Template.currentData();
+    if (!list) return 0;
+    const boardId = Session.get('currentBoard');
+    const fields = ReactiveCache.getCustomFields({
+      boardIds: { $in: [boardId] },
+      showSumAtTopOfList: true,
+      type: 'number',
+    });
+    if (!fields || !fields.length) return 0;
+    const cards = ReactiveCache.getCards({ listId: list._id, archived: false });
+    let total = 0;
+    if (cards && cards.length) {
+      cards.forEach(card => {
+        const cfs = (card.customFields || []);
+        fields.forEach(field => {
+          const cf = cfs.find(f => f && f._id === field._id);
+          if (!cf || cf.value === null || cf.value === undefined) return;
+          let v = cf.value;
+          if (typeof v === 'string') {
+            const parsed = parseFloat(v.replace(',', '.'));
+            if (isNaN(parsed)) return;
+            v = parsed;
+          }
+          if (typeof v === 'number' && isFinite(v)) {
+            total += v;
+          }
+        });
+      });
+    }
+    return total;
+  },
+  hasNumberFieldsSum() {
+    const boardId = Session.get('currentBoard');
+    const fields = ReactiveCache.getCustomFields({
+      boardIds: { $in: [boardId] },
+      showSumAtTopOfList: true,
+      type: 'number',
+    });
+    return !!(fields && fields.length);
+  },
 });
 
 Template.listActionPopup.helpers({

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

@@ -3,6 +3,6 @@ template(name="minilist")
     class="minicard-{{colorClass}}")
     .minicard-title
       .handle
-        .fa.fa-arrows
+        span.drag-handle(title="{{_ 'dragList'}}") ↕️
       +viewer
         = title

+ 3 - 3
client/components/main/layouts.css

@@ -527,7 +527,7 @@ a:not(.disabled).is-active i.fa {
   
   /* Board canvas */
   .board-canvas {
-    padding: 8px;
+    padding: 0 8px 8px 0;
     overflow-x: auto;
     -webkit-overflow-scrolling: touch;
   }
@@ -675,7 +675,7 @@ a:not(.disabled).is-active i.fa {
   }
   
   .board-canvas {
-    padding: 12px;
+    padding: 0 12px 12px 0;
   }
   
   #header {
@@ -756,7 +756,7 @@ a:not(.disabled).is-active i.fa {
 .inline-input {
   height: 37px;
   margin: 8px 10px 0 0;
-  width: 50px;
+  width: 100px;
 }
 .select-authentication {
   width: 100%;

+ 4 - 4
client/components/notifications/notification.css

@@ -20,10 +20,10 @@
   height: 3vw;
 }
 #notifications-drawer .notification .read-status .activity-type {
-  margin: 2vh 0 0;
-  width: 2.2vw;
-  height: 2.2vw;
-  font-size: clamp(14px, 2.5vw, 17px);
+  margin: 8px 0 0;
+  width: 1.2em;
+  height: 1.2em;
+  font-size: clamp(14px, 2vw, 17px);
   display: block;
   color: #bbb;
 }

+ 1 - 1
client/components/notifications/notification.jade

@@ -7,4 +7,4 @@ template(name='notification')
       +activity(activity=activityData mode='none')
     if read
       .remove
-        a.fa.fa-trash
+        a(title="{{_ 'delete'}}") 🗑️

+ 14 - 14
client/components/notifications/notificationIcon.jade

@@ -1,8 +1,8 @@
 template(name='notificationIcon')
   if($in activityType 'deleteAttachment' 'addAttachment')
-    i.fa.fa-paperclip.activity-type(title="attachment")
+    span.activity-type(title="attachment") 📎
   else if($in activityType 'createBoard' 'importBoard')
-    i.fa.fa-chalkboard.activity-type(title="board")
+    span.activity-type(title="board") 🗂️
 
   else if($in activityType 'createCard' 'importCard' 'moveCard')
     +cardNotificationIcon
@@ -19,17 +19,17 @@ template(name='notificationIcon')
     //- DRY and consistant
 
   else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
-    i.fa.fa-check-square.activity-type(title="checklist item")
+    span.activity-type(title="checklist item") ☑️
   else if($in activityType 'addComment')
-    i.fa.fa-comment-o.activity-type(title="comment")
+    span.activity-type(title="comment") 💬
   else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
-    i.fa.fa-code.activity-type(title="custom field")
+    span.activity-type(title="custom field") 🧩
   else if($in activityType 'addedLabel' 'removedLabel')
-    i.fa.fa-tag.activity-type(title="label")
+    span.activity-type(title="label") 🏷️
   else if($in activityType 'a-startAt' 'a-receivedAt')
-    i.fa.fa-clock-o.activity-type(title="date")
+    span.activity-type(title="date") ⏰
   else if($in activityType 'a-dueAt' 'a-endAt')
-    i.fa.fa-clock-o.activity-type(title="date")
+    span.activity-type(title="date") ⏰
 
   else if($in activityType 'createList' 'removeList' 'archivedList')
     +listNotificationIcon
@@ -41,17 +41,17 @@ template(name='notificationIcon')
     //- elswhere in the app we use fa-trello to indicate lists...
     //- i personally like fa-columns a bit better
   else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
-    i.fa.fa-user.activity-type(title="member")
+    span.activity-type(title="member") 👤
   else if($in activityType 'createSwimlane' 'archivedSwimlane')
-    i.fa.fa-th-large.activity-type(title="swimlane")
+    span.activity-type(title="swimlane") 🧭
   else
-    i.fa.fa-bug.activity-type(title="can't find icon for #{activityType}")
+    span.activity-type(title="can't find icon for #{activityType}") 🐞
 
 template(name='cardNotificationIcon')
-  i.fa.fa-clone.activity-type(title="card")
+  span.activity-type(title="card") 🗒️
 
 template(name='checklistNotificationIcon')
-  i.fa.fa-list.activity-type(title="checklist")
+  span.activity-type(title="checklist") 📝
 
 template(name='listNotificationIcon')
-  i.fa.fa-columns.activity-type(title="list")
+  span.activity-type(title="list") 📋

+ 0 - 3
client/components/notifications/notificationsDrawer.css

@@ -55,9 +55,6 @@ section#notifications-drawer .remove-read {
 section#notifications-drawer .remove-read:hover {
   color: #eb4646 !important;
 }
-section#notifications-drawer .remove-read:hover i.fa {
-  color: inherit;
-}
 section#notifications-drawer ul.notifications {
   display: block;
   padding: 0px 16px 0px 16px;

+ 2 - 2
client/components/notifications/notificationsDrawer.jade

@@ -8,7 +8,7 @@ template(name='notificationsDrawer')
       h5 {{_ 'notifications'}}
         if($gt unreadNotifications 0)
           |(#{unreadNotifications})
-      a.fa.fa-times-thin.close
+      a.close
     ul.notifications
       each transformedProfile.notifications
         +notification(activityData=activityObj index=dbIndex read=read)
@@ -16,5 +16,5 @@ template(name='notificationsDrawer')
       a.all-read {{_ 'mark-all-as-read'}}
     if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
       a.remove-read
-        i.fa.fa-trash
+        | 🗑️
         | {{_ 'remove-all-read'}}

+ 11 - 11
client/components/sidebar/sidebarFilters.jade

@@ -36,7 +36,7 @@ template(name="filterSidebar")
             else
               span.quiet {{_ "label-default" (_ (concat "color-" color))}}
           if Filter.labelIds.isSelected _id
-            i.fa.fa-check
+            | ✅
   hr
   h3
     | 👥
@@ -68,7 +68,7 @@ template(name="filterSidebar")
         span.sidebar-list-item-description
           | {{_ 'filter-no-assignee'}}
         if Filter.assignees.isSelected undefined
-          i.fa.fa-check
+          | ✅
     each currentBoard.activeMembers
       with getUser userId
         li(class="{{#if Filter.assignees.isSelected _id}}active{{/if}}")
@@ -90,37 +90,37 @@ template(name="filterSidebar")
         span.sidebar-list-item-description
           | {{_ 'filter-no-due-date' }}
         if Filter.dueAt.isSelected 'noDate'
-          i.fa.fa-check
+          | ✅
     li(class="{{#if Filter.dueAt.isSelected 'past'}}active{{/if}}")
       a.name.js-toggle-overdue-filter
         span.sidebar-list-item-description
           | {{_ 'filter-overdue' }}
         if Filter.dueAt.isSelected 'past'
-          i.fa.fa-check
+          | ✅
     li(class="{{#if Filter.dueAt.isSelected 'today'}}active{{/if}}")
       a.name.js-toggle-due-today-filter
         span.sidebar-list-item-description
           | {{_ 'filter-due-today' }}
         if Filter.dueAt.isSelected 'today'
-          i.fa.fa-check
+          | ✅
     li(class="{{#if Filter.dueAt.isSelected 'tomorrow'}}active{{/if}}")
       a.name.js-toggle-due-tomorrow-filter
         span.sidebar-list-item-description
           | {{_ 'filter-due-tomorrow' }}
         if Filter.dueAt.isSelected 'tomorrow'
-          i.fa.fa-check
+          | ✅
     li(class="{{#if Filter.dueAt.isSelected 'thisweek'}}active{{/if}}")
       a.name.js-toggle-due-this-week-filter
         span.sidebar-list-item-description
           | {{_ 'filter-due-this-week' }}
         if Filter.dueAt.isSelected 'thisweek'
-          i.fa.fa-check
+          | ✅
     li(class="{{#if Filter.dueAt.isSelected 'nextweek'}}active{{/if}}")
       a.name.js-toggle-due-next-week-filter
         span.sidebar-list-item-description
           | {{_ 'filter-due-next-week' }}
         if Filter.dueAt.isSelected 'nextweek'
-          i.fa.fa-check
+          | ✅
   hr
   h3
     | 📋
@@ -138,7 +138,7 @@ template(name="filterSidebar")
           span.sidebar-list-item-description
             | {{ name }}
           if Filter.customFields.isSelected _id
-            i.fa.fa-check
+            | ✅
   hr
   h3
     | {{_ 'other-filters-label'}}
@@ -148,14 +148,14 @@ template(name="filterSidebar")
         span.sidebar-list-item-description
           | {{_ 'filter-show-archive'}}
         if Filter.archive.isSelected _id
-          i.fa.fa-check
+          | ✅
   ul.sidebar-list
     li(class="{{#if Filter.hideEmpty.isSelected _id}}active{{/if}}")
       a.name.js-toggle-hideEmpty-filter
         span.sidebar-list-item-description
           | {{_ 'filter-hide-empty'}}
         if Filter.hideEmpty.isSelected _id
-          i.fa.fa-check
+          | ✅
   hr
   h3 {{_ 'advanced-filter-label'}}
   input.js-field-advanced-filter(type="text")

+ 2 - 11
client/components/swimlanes/swimlaneHeader.jade

@@ -28,23 +28,14 @@ template(name="swimlaneFixedHeader")
         unless currentUser.isWorker
           a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
             | ➕
-          a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
-            | ☰
-          //// TODO: Collapse Swimlane: make button working, etc.
-          //unless collapsed
-          //  a.js-collapse-swimlane(title="{{_ 'collapse'}}")
-          //    i.fa.fa-arrow-down.swimlane-header-collapse-down
-          //    ⬆️.swimlane-header-collapse-up
-          //if collapsed
-          //  a.js-collapse-swimlane(title="{{_ 'uncollapse'}}")
-          //    ⬆️.swimlane-header-collapse-up
-          //    i.fa.fa-arrow-down.swimlane-header-collapse-down
           unless isTouchScreen
             a.swimlane-header-handle.handle.js-swimlane-header-handle
               | ↕️
           if isTouchScreen
             a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
               | ↕️
+          a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
+            | ☰
 
 template(name="editSwimlaneTitleForm")
   .list-composer

+ 24 - 19
client/components/swimlanes/swimlanes.css

@@ -54,12 +54,14 @@
   width: 100%;
   min-width: 100%;
   position: relative;
-  overflow: hidden;
+  overflow: visible;
   min-height: 33px;
+  padding: 0;
+  margin: 0;
 }
 .swimlane .swimlane-header-wrap .swimlane-header {
   font-size: 14px;
-  padding: 5px 5px;
+  padding: 0;
   font-weight: bold;
   min-height: 33px;
   width: 100%;
@@ -74,30 +76,39 @@
 }
 .swimlane .swimlane-header-wrap .swimlane-header-menu {
   position: absolute;
-  padding: 5px 5px;
+  top: 0;
+  left: 0;
+  padding: 0;
+  margin: 0;
   font-size: 22px;
+  line-height: 1;
   z-index: 20;
   pointer-events: auto;
 }
+.swimlane .swimlane-header-wrap .swimlane-header-menu .js-open-swimlane-menu {
+  top: calc(50% + 6px);
+  padding: 5px;
+  display: inline-block;
+  margin-left: 74px;
+}
 @media print {
   .swimlane .swimlane-header-wrap .swimlane-header-menu {
     display: none;
   }
 }
 .swimlane .swimlane-header-wrap .swimlane-header-plus-icon {
-  margin-left: 5px;
-  padding-right: 20px;
+  top: calc(50% + 6px);
+  padding: 5px;
   font-size: 22px;
 }
 .swimlane .swimlane-header-wrap .swimlane-header-menu-icon {
-  padding-right: 20px;
+  top: calc(50% + 6px);
+  padding: 5px;
   font-size: 22px;
 }
 .swimlane .swimlane-header-wrap .swimlane-header-handle {
-  position: absolute;
-  padding: 7px;
-  top: 50%;
-  left: 230px;
+  top: calc(50% + 2px);
+  padding: 2px;
   font-size: clamp(16px, 3vw, 20px);
   transform: translateY(-50%);
   display: flex;
@@ -109,22 +120,16 @@
 }
 .swimlane .swimlane-header-wrap .swimlane-header-miniscreen-handle {
   position: absolute;
-  padding: 7px;
-  top: 50%;
+  padding: 2px;
+  top: calc(50% + 2px);
   transform: translateY(-50%);
-  right: 10px;
+  right: 60px;
   font-size: 24px;
   cursor: move;
   z-index: 15;
   pointer-events: auto;
 }
 
-/* Safety: ensure wrapper is interactive and above list content */
-.swimlane .swimlane-header-wrap {
-  position: relative;
-  z-index: 9;
-  pointer-events: auto;
-}
 #js-swimlane-height-edit .swimlane-height-error {
   display: none;
 }

+ 4 - 4
client/components/users/userAvatar.css

@@ -3,8 +3,8 @@
   display: block;
   position: relative;
   float: left;
-  height: 30px;
-  width: 30px;
+  height: clamp(24px, 3.5vw, 36px);
+  width: clamp(24px, 3.5vw, 36px);
   margin: .3vh;
   cursor: pointer;
   user-select: none;
@@ -111,7 +111,7 @@
   padding-top: 0;
 }
 .mini-profile-info .member {
-  width: 50px;
-  height: 50px;
+  width: clamp(40px, 5vw, 60px);
+  height: clamp(40px, 5vw, 60px);
   margin-right: 10px;
 }