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

Thanks to xet7 !

Fixes #3796
This commit is contained in:
Lauri Ojansivu 2025-12-22 21:06:44 +02:00
parent 4292302c3c
commit e569c2957e
29 changed files with 239 additions and 142 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -223,9 +223,13 @@
.card-label-edit-button:hover { .card-label-edit-button:hover {
background: #dbdbdb; background: #dbdbdb;
} }
ul.edit-labels-pop-over span.fa.label-handle { ul.edit-labels-pop-over span.label-handle {
padding-right: 10px; 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; max-width: 180px;
} }

View file

@ -31,7 +31,7 @@ template(name="cardLabelsPopup")
a.card-label-edit-button.js-edit-label a.card-label-edit-button.js-edit-label
| ✏️ | ✏️
if isTouchScreenOrShowDesktopDragHandles 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}}" span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}") class="{{# if isLabelSelected ../_id }}active{{/if}}")
+viewer +viewer

View file

@ -142,9 +142,12 @@
display: block; display: block;
} }
} }
.minicard .handle .fa-arrows { .minicard .handle .drag-handle {
font-size: clamp(16px, 3vw, 20px); font-size: clamp(16px, 3vw, 20px);
color: #ccc; color: #ccc;
display: inline-block;
width: 1.4em;
text-align: center;
} }
.minicard .minicard-title .card-number { .minicard .minicard-title .card-number {
color: #b3b3b3; color: #b3b3b3;
@ -297,19 +300,6 @@
background-color: #1976d2 !important; 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 { .minicard .badges {
float: left; float: left;
margin-top: 1vh; margin-top: 1vh;
@ -740,8 +730,3 @@
align-items: center; align-items: center;
gap: 0.3vw; gap: 0.3vw;
} }
.minicard-list-name i.fa {
font-size: 0.8em;
opacity: 0.7;
}

View file

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

View file

@ -2,19 +2,17 @@
<div class="original-position-info"> <div class="original-position-info">
{{#if isLoading}} {{#if isLoading}}
<div class="original-position-loading"> <div class="original-position-loading">
<i class="fa fa-spinner fa-spin"></i> Loading original position... Loading original position...
</div> </div>
{{else if showOriginalPosition}} {{else if showOriginalPosition}}
<div class="original-position-details"> <div class="original-position-details">
{{#if hasMovedFromOriginal}} {{#if hasMovedFromOriginal}}
<div class="original-position-moved"> <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> </div>
{{else}} {{else}}
<div class="original-position-unchanged"> <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> </div>
{{/if}} {{/if}}

View file

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

View file

@ -368,6 +368,18 @@ body.list-resizing-active * {
text-overflow: ellipsis; text-overflow: ellipsis;
word-wrap: break-word; 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 { .list-rotated {
width: 1.3vw; width: 1.3vw;
height: 35vh; height: 35vh;
@ -750,6 +762,9 @@ body.list-resizing-active * {
grid-row: 2; grid-row: 2;
grid-column: 2; grid-column: 2;
align-self: start; align-self: start;
text-align: left;
padding-left: 0;
margin-left: 0;
font-size: 16px !important; font-size: 16px !important;
line-height: 1.2; line-height: 1.2;
} }
@ -964,6 +979,9 @@ body.list-resizing-active * {
grid-row: 2 !important; grid-row: 2 !important;
grid-column: 2 !important; grid-column: 2 !important;
align-self: start !important; align-self: start !important;
text-align: left !important;
padding-left: 0 !important;
margin-left: 0 !important;
font-size: 16px !important; font-size: 16px !important;
line-height: 1.2 !important; line-height: 1.2 !important;
} }

View file

@ -16,11 +16,50 @@ BlazeComponent.extendComponent({
}, },
customFieldsSum() { customFieldsSum() {
const ret = ReactiveCache.getCustomFields({ const list = Template.currentData();
boardIds: { $in: [Session.get('currentBoard')] }, if (!list) return [];
const boardId = Session.get('currentBoard');
const fields = ReactiveCache.getCustomFields({
boardIds: { $in: [boardId] },
showSumAtTopOfList: true, 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) { openForm(options) {
@ -254,6 +293,22 @@ BlazeComponent.extendComponent({
}, },
}).register('listBody'); }).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) { function toggleValueInReactiveArray(reactiveValue, value) {
const array = reactiveValue.get(); const array = reactiveValue.get();
const valueIndex = array.indexOf(value); const valueIndex = array.indexOf(value);

View file

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

View file

@ -142,7 +142,48 @@ BlazeComponent.extendComponent({
Template.listHeader.helpers({ Template.listHeader.helpers({
isBoardAdmin() { isBoardAdmin() {
return ReactiveCache.getCurrentUser().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({ Template.listActionPopup.helpers({

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
template(name='notificationIcon') template(name='notificationIcon')
if($in activityType 'deleteAttachment' 'addAttachment') if($in activityType 'deleteAttachment' 'addAttachment')
i.fa.fa-paperclip.activity-type(title="attachment") span.activity-type(title="attachment") 📎
else if($in activityType 'createBoard' 'importBoard') 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') else if($in activityType 'createCard' 'importCard' 'moveCard')
+cardNotificationIcon +cardNotificationIcon
@ -19,17 +19,17 @@ template(name='notificationIcon')
//- DRY and consistant //- DRY and consistant
else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem') 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') 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') 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') 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') 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') 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') else if($in activityType 'createList' 'removeList' 'archivedList')
+listNotificationIcon +listNotificationIcon
@ -41,17 +41,17 @@ template(name='notificationIcon')
//- elswhere in the app we use fa-trello to indicate lists... //- elswhere in the app we use fa-trello to indicate lists...
//- i personally like fa-columns a bit better //- i personally like fa-columns a bit better
else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember') 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') else if($in activityType 'createSwimlane' 'archivedSwimlane')
i.fa.fa-th-large.activity-type(title="swimlane") span.activity-type(title="swimlane") 🧭
else 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') template(name='cardNotificationIcon')
i.fa.fa-clone.activity-type(title="card") span.activity-type(title="card") 🗒️
template(name='checklistNotificationIcon') template(name='checklistNotificationIcon')
i.fa.fa-list.activity-type(title="checklist") span.activity-type(title="checklist") 📝
template(name='listNotificationIcon') template(name='listNotificationIcon')
i.fa.fa-columns.activity-type(title="list") span.activity-type(title="list") 📋

View file

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

View file

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

View file

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

View file

@ -28,23 +28,14 @@ template(name="swimlaneFixedHeader")
unless currentUser.isWorker unless currentUser.isWorker
a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}") 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 unless isTouchScreen
a.swimlane-header-handle.handle.js-swimlane-header-handle a.swimlane-header-handle.handle.js-swimlane-header-handle
| ↕️ | ↕️
if isTouchScreen if isTouchScreen
a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
| ↕️ | ↕️
a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
| ☰
template(name="editSwimlaneTitleForm") template(name="editSwimlaneTitleForm")
.list-composer .list-composer

View file

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

View file

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