mirror of
https://github.com/wekan/wekan.git
synced 2026-02-09 01:34:21 +01:00
Resolve merge conflicts by accepting PR #6131 changes
Co-authored-by: xet7 <15545+xet7@users.noreply.github.com>
This commit is contained in:
parent
dc0b68ee80
commit
97dd5d2064
257 changed files with 9483 additions and 14103 deletions
|
|
@ -1,16 +1,22 @@
|
|||
.activity-title {
|
||||
margin: 0 0.7vw 1vh;
|
||||
display: flex;
|
||||
gap: 0.5lh;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.reactions-popup {
|
||||
display: flex;
|
||||
gap: 1ch;
|
||||
flex-wrap: wrap;
|
||||
margin: 0.5lh 0.5ch;
|
||||
max-width: 80vw;
|
||||
}
|
||||
.reactions-popup .add-comment-reaction {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
border-radius: 0.7vw;
|
||||
font-size: clamp(18px, 4vw, 22px);
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
width: 5vw;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.reactions-popup .add-comment-reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
|
|
@ -18,20 +24,20 @@
|
|||
.activities {
|
||||
clear: both;
|
||||
}
|
||||
.activity {
|
||||
display: flex;
|
||||
}
|
||||
.activities .activity {
|
||||
margin: 0.1vh 0;
|
||||
padding: 0.8vh 0;
|
||||
display: flex;
|
||||
}
|
||||
.activities .activity .member {
|
||||
width: 4vw;
|
||||
height: 4vw;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.activities .activity .activity-member {
|
||||
font-weight: 700;
|
||||
}
|
||||
.activities .activity .activity-desc {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ Template.commentReactions.events({
|
|||
cardComment.toggleReaction(codepoint);
|
||||
}
|
||||
},
|
||||
'click .open-comment-reaction-popup': Popup.open('addReaction'),
|
||||
'click .open-comment-reaction-popup': Popup.open('addReaction', {showHeader: false})
|
||||
})
|
||||
|
||||
Template.addReactionPopup.events({
|
||||
|
|
@ -306,6 +306,11 @@ Template.addReactionPopup.helpers({
|
|||
'😊',
|
||||
'🤔',
|
||||
'😔'];
|
||||
},
|
||||
hasUserReacted(codepoint) {
|
||||
const commentId = Template.instance().data.commentId;
|
||||
const cardComment = ReactiveCache.getCardComment(commentId);
|
||||
return cardComment.hasUserReacted(codepoint);
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
.new-comment {
|
||||
position: relative;
|
||||
margin: 0 0 20px 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: stretch;
|
||||
gap: 1ch;
|
||||
}
|
||||
.new-comment .member {
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: -38px;
|
||||
}
|
||||
.new-comment.is-open .member {
|
||||
opacity: 1;
|
||||
|
|
@ -14,34 +14,44 @@
|
|||
.new-comment.is-open .helper {
|
||||
display: inline-block;
|
||||
}
|
||||
.new-comment.is-open textarea {
|
||||
min-height: 100px;
|
||||
color: #4d4d4d;
|
||||
cursor: auto;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
|
||||
.new-comment, .comment {
|
||||
.is-open textarea {
|
||||
min-height: 100px;
|
||||
color: #4d4d4d;
|
||||
cursor: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
textarea {
|
||||
grid-area: editor;
|
||||
background-color: #fff;
|
||||
border: 0;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
|
||||
min-height: 3lh;
|
||||
&:hover, &.is-open {
|
||||
cursor: auto;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.33);
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-comment .too-long {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.new-comment textarea {
|
||||
background-color: #fff;
|
||||
border: 0;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
|
||||
height: 36px;
|
||||
margin: 4px 4px 6px 0;
|
||||
padding: 9px 11px;
|
||||
width: 100%;
|
||||
}
|
||||
.new-comment textarea:hover,
|
||||
.new-comment textarea:is-open {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.33);
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.new-comment textarea:is-open {
|
||||
cursor: auto;
|
||||
|
||||
.js-new-comment-form, .js-edit-comment {
|
||||
display: grid !important;
|
||||
grid-template-areas:
|
||||
"editor editor editor editor"
|
||||
"main-controls main-controls link-controls editor-controls";
|
||||
grid-auto-columns: 1fr;
|
||||
grid-auto-rows: min-content;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
gap: 0.3lh;
|
||||
}
|
||||
.comment-item {
|
||||
background-color: #fff;
|
||||
|
|
@ -65,31 +75,30 @@
|
|||
}
|
||||
.comments {
|
||||
clear: both;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1lh;
|
||||
padding-top: 1lh;
|
||||
}
|
||||
.comments .comment {
|
||||
margin: 0.5px 0;
|
||||
padding: 6px 0;
|
||||
display: flex;
|
||||
gap: 1ch;
|
||||
}
|
||||
.comments .comment .member {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.comments .comment .comment-member {
|
||||
font-weight: 700;
|
||||
}
|
||||
.comments .comment .comment-desc {
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
margin-left: 3px;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3lh;
|
||||
}
|
||||
.comments .comment .comment-desc .comment-text {
|
||||
display: block;
|
||||
display: flex;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
|
|
@ -101,6 +110,7 @@
|
|||
display: flex;
|
||||
margin-top: 5px;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup {
|
||||
display: flex;
|
||||
|
|
@ -110,7 +120,6 @@
|
|||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup span {
|
||||
display: inline-block;
|
||||
font-size: clamp(14px, 2vw, 18px);
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-left: 4px;
|
||||
|
|
@ -128,10 +137,14 @@
|
|||
.comments .comment .comment-desc .reactions .reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction .reaction-count {
|
||||
font-size: 12px;
|
||||
}
|
||||
.comments .comment .comment-desc .comment-meta {
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: max-content;
|
||||
gap: 1ch;
|
||||
align-items: center;
|
||||
/* #FIXME maybe put date outside of comment body ?*/
|
||||
margin-inline-start: -10vw;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,5 +64,5 @@ template(name="commentReactions")
|
|||
template(name="addReactionPopup")
|
||||
.reactions-popup
|
||||
each codepoint in codepoints
|
||||
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
|
||||
|
||||
unless (hasUserReacted codepoint)
|
||||
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
.board-conversion-modal {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
.board-conversion-header h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
.board-conversion-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.board-conversion-content {
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #2196F3, #21CBF3);
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
|
@ -116,14 +116,14 @@
|
|||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #2196F3;
|
||||
font-size: 16px;
|
||||
|
||||
}
|
||||
|
||||
.conversion-status {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
|
||||
}
|
||||
|
||||
.conversion-status i {
|
||||
|
|
@ -134,10 +134,10 @@
|
|||
.conversion-time {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
background-color: #f5f5f5;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +155,7 @@
|
|||
.conversion-info {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
|
@ -170,15 +170,15 @@
|
|||
width: 95%;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
|
||||
.board-conversion-header,
|
||||
.board-conversion-content,
|
||||
.board-conversion-footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
|
||||
.board-conversion-header h3 {
|
||||
font-size: 18px;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
template(name="archivedBoards")
|
||||
h2
|
||||
span(title="{{_ 'archived-boards'}}")
|
||||
i.fa.fa-archive
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archived-boards'}}
|
||||
|
||||
ul.archived-lists
|
||||
|
|
|
|||
|
|
@ -1,43 +1,25 @@
|
|||
.swim-flex {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding-bottom: 40vw;
|
||||
}
|
||||
|
||||
.board-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* When zoom is 50% or lower, ensure full width like content */
|
||||
.board-wrapper[style*="transform: scale(0.5)"] {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper[style*="transform: scale(0.4)"] {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper[style*="transform: scale(0.3)"] {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.board-wrapper .board-canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
transition: margin 0.1s;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
/* don't stretch vertically if not needed (e.g collapsed) */
|
||||
align-self: start;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Ensure horizontal scrollbar is visible for high zoom levels */
|
||||
|
|
@ -97,172 +79,12 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
#content[style*="overflow-x: auto"]::-webkit-scrollbar {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
|
||||
/* Force vertical scrollbar to always be visible */
|
||||
#content[style*="overflow-y: auto"] {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
/* Force vertical scrollbar to always be visible */
|
||||
#content[style*="overflow-y: auto"] {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
/* Mobile - make all text 2x bigger inside #content by default (icons stay same size) */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
#content {
|
||||
font-size: 2em !important; /* 2x bigger base font size for content area */
|
||||
}
|
||||
|
||||
/* Make all text elements 2x bigger */
|
||||
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6,
|
||||
#content p, #content span, #content div, #content a, #content button,
|
||||
#content .minicard, #content .list-header-name, #content .board-header-btn,
|
||||
#content .card-title, #content .card-details, #content .card-description,
|
||||
#content .swimlane-header, #content .list-title, #content .card-text,
|
||||
#content .member, #content .member-name, #content .member-initials,
|
||||
#content .checklist-item, #content .checklist-title, #content .comment,
|
||||
#content .activity, #content .activity-text, #content .activity-time,
|
||||
#content .board-title, #content .board-description, #content .list-name,
|
||||
#content .card-text, #content .card-title, #content .card-description,
|
||||
#content .swimlane-title, #content .swimlane-description,
|
||||
#content .board-header-title, #content .board-header-description,
|
||||
#content .card-detail-title, #content .card-detail-description,
|
||||
#content .list-header-title, #content .list-header-description,
|
||||
#content .swimlane-header-title, #content .swimlane-header-description,
|
||||
#content .minicard-title, #content .minicard-description,
|
||||
#content .card-comment, #content .card-comment-text,
|
||||
#content .checklist-item-text, #content .checklist-item-title,
|
||||
#content .activity-item, #content .activity-item-text,
|
||||
#content .board-member, #content .board-member-name,
|
||||
#content .team-member, #content .team-member-name,
|
||||
#content .org-member, #content .org-member-name,
|
||||
#content .template-member, #content .template-member-name,
|
||||
#content .user-name, #content .user-email, #content .user-role,
|
||||
#content .setting-title, #content .setting-description,
|
||||
#content .popup-title, #content .popup-description,
|
||||
#content .modal-title, #content .modal-description,
|
||||
#content .notification-title, #content .notification-text,
|
||||
#content .announcement-title, #content .announcement-text,
|
||||
#content .offline-warning-title, #content .offline-warning-text,
|
||||
#content .error-title, #content .error-text,
|
||||
#content .success-title, #content .success-text,
|
||||
#content .info-title, #content .info-text,
|
||||
#content .warning-title, #content .warning-text {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* Keep icons the same size (don't scale them) */
|
||||
#content .fa, #content .icon, #content i {
|
||||
font-size: 1em !important; /* Keep original icon size */
|
||||
}
|
||||
|
||||
/* Reset specific icon sizes to prevent double scaling */
|
||||
#content .fa-home, #content .fa-bars, #content .fa-search,
|
||||
#content .fa-bell, #content .fa-user, #content .fa-cog,
|
||||
#content .fa-plus, #content .fa-minus, #content .fa-edit,
|
||||
#content .fa-trash, #content .fa-save, #content .fa-cancel,
|
||||
#content .fa-arrow-left, #content .fa-arrow-right,
|
||||
#content .fa-arrow-up, #content .fa-arrow-down,
|
||||
#content .fa-check, #content .fa-times, #content .fa-close,
|
||||
#content .fa-star, #content .fa-heart, #content .fa-thumbs-up,
|
||||
#content .fa-thumbs-down, #content .fa-comment, #content .fa-reply,
|
||||
#content .fa-share, #content .fa-download, #content .fa-upload,
|
||||
#content .fa-copy, #content .fa-paste, #content .fa-cut,
|
||||
#content .fa-undo, #content .fa-redo, #content .fa-refresh,
|
||||
#content .fa-sync, #content .fa-spinner, #content .fa-loading,
|
||||
#content .fa-info, #content .fa-question, #content .fa-exclamation,
|
||||
#content .fa-warning, #content .fa-error, #content .fa-success,
|
||||
#content .fa-check-circle, #content .fa-times-circle,
|
||||
#content .fa-exclamation-circle, #content .fa-question-circle,
|
||||
#content .fa-info-circle, #content .fa-warning-circle,
|
||||
#content .fa-error-circle, #content .fa-success-circle {
|
||||
font-size: 1em !important; /* Keep original icon size */
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback for iPhone devices using JavaScript detection */
|
||||
.iphone-device #content {
|
||||
font-size: 2em !important; /* 2x bigger base font size for content area */
|
||||
}
|
||||
|
||||
.iphone-device #content h1, .iphone-device #content h2, .iphone-device #content h3, .iphone-device #content h4, .iphone-device #content h5, .iphone-device #content h6,
|
||||
.iphone-device #content p, .iphone-device #content span, .iphone-device #content div, .iphone-device #content a, .iphone-device #content button,
|
||||
.iphone-device #content .minicard, .iphone-device #content .list-header-name, .iphone-device #content .board-header-btn,
|
||||
.iphone-device #content .card-title, .iphone-device #content .card-details, .iphone-device #content .card-description,
|
||||
.iphone-device #content .swimlane-header, .iphone-device #content .list-title, .iphone-device #content .card-text,
|
||||
.iphone-device #content .member, .iphone-device #content .member-name, .iphone-device #content .member-initials,
|
||||
.iphone-device #content .checklist-item, .iphone-device #content .checklist-title, .iphone-device #content .comment,
|
||||
.iphone-device #content .activity, .iphone-device #content .activity-text, .iphone-device #content .activity-time,
|
||||
.iphone-device #content .board-title, .iphone-device #content .board-description, .iphone-device #content .list-name,
|
||||
.iphone-device #content .card-text, .iphone-device #content .card-title, .iphone-device #content .card-description,
|
||||
.iphone-device #content .swimlane-title, .iphone-device #content .swimlane-description,
|
||||
.iphone-device #content .board-header-title, .iphone-device #content .board-header-description,
|
||||
.iphone-device #content .card-detail-title, .iphone-device #content .card-detail-description,
|
||||
.iphone-device #content .list-header-title, .iphone-device #content .list-header-description,
|
||||
.iphone-device #content .swimlane-header-title, .iphone-device #content .swimlane-header-description,
|
||||
.iphone-device #content .minicard-title, .iphone-device #content .minicard-description,
|
||||
.iphone-device #content .card-comment, .iphone-device #content .card-comment-text,
|
||||
.iphone-device #content .checklist-item-text, .iphone-device #content .checklist-item-title,
|
||||
.iphone-device #content .activity-item, .iphone-device #content .activity-item-text,
|
||||
.iphone-device #content .board-member, .iphone-device #content .board-member-name,
|
||||
.iphone-device #content .team-member, .iphone-device #content .team-member-name,
|
||||
.iphone-device #content .org-member, .iphone-device #content .org-member-name,
|
||||
.iphone-device #content .template-member, .iphone-device #content .template-member-name,
|
||||
.iphone-device #content .user-name, .iphone-device #content .user-email, .iphone-device #content .user-role,
|
||||
.iphone-device #content .setting-title, .iphone-device #content .setting-description,
|
||||
.iphone-device #content .popup-title, .iphone-device #content .popup-description,
|
||||
.iphone-device #content .modal-title, .iphone-device #content .modal-description,
|
||||
.iphone-device #content .notification-title, .iphone-device #content .notification-text,
|
||||
.iphone-device #content .announcement-title, .iphone-device #content .announcement-text,
|
||||
.iphone-device #content .offline-warning-title, .iphone-device #content .offline-warning-text,
|
||||
.iphone-device #content .error-title, .iphone-device #content .error-text,
|
||||
.iphone-device #content .success-title, .iphone-device #content .success-text,
|
||||
.iphone-device #content .info-title, .iphone-device #content .info-text,
|
||||
.iphone-device #content .warning-title, .iphone-device #content .warning-text {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* Keep icons the same size for iPhone devices */
|
||||
.iphone-device #content .fa, .iphone-device #content .icon, .iphone-device #content i {
|
||||
font-size: 1em !important; /* Keep original icon size */
|
||||
}
|
||||
|
||||
/* Mobile iPhone: scale card details text and icons to 2x */
|
||||
body.mobile-mode.iphone-device .card-details {
|
||||
font-size: 2em !important;
|
||||
}
|
||||
body.mobile-mode.iphone-device .card-details .fa,
|
||||
body.mobile-mode.iphone-device .card-details .icon,
|
||||
body.mobile-mode.iphone-device .card-details i,
|
||||
body.mobile-mode.iphone-device .card-details .emoji-icon,
|
||||
body.mobile-mode.iphone-device .card-details a,
|
||||
body.mobile-mode.iphone-device .card-details p,
|
||||
body.mobile-mode.iphone-device .card-details span,
|
||||
body.mobile-mode.iphone-device .card-details div,
|
||||
body.mobile-mode.iphone-device .card-details button,
|
||||
body.mobile-mode.iphone-device .card-details input,
|
||||
body.mobile-mode.iphone-device .card-details select,
|
||||
body.mobile-mode.iphone-device .card-details textarea {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
/* Section titles slightly larger than content but not as big as card title */
|
||||
body.mobile-mode.iphone-device .card-details .card-details-item-title {
|
||||
font-size: 1.1em !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Ensure scrollbars are positioned correctly */
|
||||
#content[style*="overflow-x: auto"]::-webkit-scrollbar:vertical {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
#content[style*="overflow-x: auto"]::-webkit-scrollbar:horizontal {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Force both scrollbars to always be visible for high zoom levels */
|
||||
#content[style*="overflow-x: auto"][style*="overflow-y: auto"] {
|
||||
|
|
@ -274,36 +96,6 @@ body.mobile-mode.iphone-device .card-details .card-details-item-title {
|
|||
#content[style*="overflow-y: auto"] {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
.board-wrapper .board-canvas .board-overlay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
top: -100px;
|
||||
right: -400px;
|
||||
background: #000;
|
||||
opacity: 0.33;
|
||||
animation: fadeIn 0.2s;
|
||||
z-index: 16;
|
||||
}
|
||||
|
||||
/* Fix for mobile Safari: ensure overlay stays behind card details */
|
||||
@media screen and (max-width: 800px) {
|
||||
.board-wrapper .board-canvas .board-overlay {
|
||||
z-index: 17 !important;
|
||||
}
|
||||
|
||||
/* In desktop mode on small screens, still keep overlay behind card */
|
||||
body.desktop-mode .board-wrapper .board-canvas .board-overlay {
|
||||
z-index: 17 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* In mobile mode, lower the overlay z-index to stay behind card details */
|
||||
body.mobile-mode .board-wrapper .board-canvas .board-overlay {
|
||||
z-index: 17 !important;
|
||||
}
|
||||
|
||||
/* iPhone in desktop mode: remove overlay to avoid blocking card */
|
||||
body.desktop-mode.iphone-device .board-wrapper .board-canvas .board-overlay {
|
||||
|
|
@ -320,73 +112,14 @@ body.desktop-mode .board-wrapper .board-canvas .board-overlay {
|
|||
.board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked {
|
||||
display: none;
|
||||
}
|
||||
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
|
||||
.board-wrapper.mobile-view {
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.board-wrapper.mobile-view .board-canvas {
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.board-wrapper.mobile-view .board-canvas.mobile-view .swimlane {
|
||||
border-bottom: 1px solid #ccc;
|
||||
display: block !important;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
|
||||
.board-wrapper {
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.board-wrapper .board-canvas {
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.board-wrapper .board-canvas .swimlane {
|
||||
border-bottom: 1px solid #ccc;
|
||||
display: block !important;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
.board-wrapper .board-canvas .swimlane {
|
||||
/* this effectively prevents board
|
||||
to shrink */
|
||||
min-width: 100vw;
|
||||
}
|
||||
}
|
||||
.calendar-event-green {
|
||||
|
|
@ -545,7 +278,6 @@ body.desktop-mode .board-wrapper .board-canvas .board-overlay {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
|
|
@ -558,10 +290,6 @@ body.desktop-mode .board-wrapper .board-canvas .board-overlay {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
font-size: 25px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
template(name="board")
|
||||
|
||||
if isConverting.get
|
||||
+boardConversionProgress
|
||||
else if isBoardReady.get
|
||||
if currentBoard
|
||||
if onlyShowCurrentCard
|
||||
+cardDetails(currentCard)
|
||||
else
|
||||
+boardBody
|
||||
+boardBody
|
||||
else
|
||||
//-- XXX We need a better error message in case the board has been archived
|
||||
+message(label="board-not-found")
|
||||
|
|
@ -17,32 +13,32 @@ template(name="board")
|
|||
|
||||
template(name="boardBody")
|
||||
if notDisplayThisBoard
|
||||
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
|
||||
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
|
||||
else
|
||||
// Debug information (remove in production)
|
||||
//- Debug information (remove in production)
|
||||
if debugBoardState
|
||||
//- Debug information (remove in production)
|
||||
.debug-info(style="position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.8); color: white; padding: 10px; z-index: 9999; font-size: 12px;")
|
||||
| {{_ 'board'}}: {{currentBoard.title}} | {{_ 'view'}}: {{boardView}} | {{_ 'has-swimlanes'}}: {{hasSwimlanes}} | {{_ 'swimlanes'}}: {{currentBoard.swimlanes.length}}
|
||||
.board-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
.board-canvas.js-swimlanes(
|
||||
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
|
||||
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
|
||||
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
|
||||
class="{{#if draggingActive.get}}is-dragging-active{{/if}}"
|
||||
class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}"
|
||||
class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
if showOverlay.get
|
||||
.board-overlay
|
||||
if currentBoard.isTemplatesBoard
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else if isViewSwimlanes
|
||||
if hasSwimlanes
|
||||
.swim-flex
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else
|
||||
// Fallback: If no swimlanes exist, show lists instead of empty message
|
||||
+listsGroup(currentBoard)
|
||||
else if isViewSwimlanes
|
||||
.swim-flex
|
||||
if hasSwimlanes
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else
|
||||
// Fallback: If no swimlanes exist, show lists instead of empty message
|
||||
+listsGroup(currentBoard)
|
||||
else if isViewLists
|
||||
+listsGroup(currentBoard)
|
||||
else if isViewCalendar
|
||||
|
|
@ -56,10 +52,6 @@ template(name="boardBody")
|
|||
+swimlane(this)
|
||||
else
|
||||
+listsGroup(currentBoard)
|
||||
//- Render multiple open cards in desktop mode
|
||||
unless isMiniScreen
|
||||
each openCards
|
||||
+cardDetails(this cardIndex=@index)
|
||||
+sidebar
|
||||
|
||||
template(name="calendarView")
|
||||
|
|
|
|||
|
|
@ -27,16 +27,16 @@ BlazeComponent.extendComponent({
|
|||
this.autorun(() => {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
|
||||
|
||||
const handle = subManager.subscribe('board', currentBoardId, false);
|
||||
|
||||
|
||||
// Use a separate autorun for subscription ready state to avoid reactive loops
|
||||
this.subscriptionReadyAutorun = Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
if (!this._boardProcessed || this._lastProcessedBoardId !== currentBoardId) {
|
||||
this._boardProcessed = true;
|
||||
this._lastProcessedBoardId = currentBoardId;
|
||||
|
||||
|
||||
// Ensure default swimlane exists (only once per board)
|
||||
this.ensureDefaultSwimlane(currentBoardId);
|
||||
// Check if board needs conversion
|
||||
|
|
@ -67,7 +67,7 @@ BlazeComponent.extendComponent({
|
|||
if (!board) return;
|
||||
|
||||
const swimlanes = board.swimlanes();
|
||||
|
||||
|
||||
if (swimlanes.length === 0) {
|
||||
// Check if any swimlane exists in the database to avoid race conditions
|
||||
const existingSwimlanes = ReactiveCache.getSwimlanes({ boardId });
|
||||
|
|
@ -105,7 +105,6 @@ BlazeComponent.extendComponent({
|
|||
this.isBoardReady.set(true); // Show board even if conversion check failed
|
||||
}
|
||||
},
|
||||
|
||||
onlyShowCurrentCard() {
|
||||
const isMiniScreen = Utils.isMiniScreen();
|
||||
const currentCardId = Utils.getCurrentCardId(true);
|
||||
|
|
@ -114,7 +113,7 @@ BlazeComponent.extendComponent({
|
|||
|
||||
openCards() {
|
||||
// In desktop mode, return array of all open cards
|
||||
const isMobile = Utils.getMobileMode();
|
||||
const isMobile = Utils.isMiniScreen();
|
||||
if (!isMobile) {
|
||||
const openCardIds = Session.get('openCards') || [];
|
||||
return openCardIds.map(id => ReactiveCache.getCard(id)).filter(card => card);
|
||||
|
|
@ -123,7 +122,7 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
goHome() {
|
||||
FlowRouter.go('home');
|
||||
FlowRouter.go('home')
|
||||
},
|
||||
|
||||
isConverting() {
|
||||
|
|
@ -195,7 +194,7 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
},
|
||||
onRendered() {
|
||||
// Initialize user settings (zoom and mobile mode)
|
||||
// Initialize user settings (mobile mode)
|
||||
Utils.initializeUserSettings();
|
||||
|
||||
// Detect iPhone devices and add class for better CSS targeting
|
||||
|
|
@ -221,9 +220,9 @@ BlazeComponent.extendComponent({
|
|||
const popupObserver = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
if (node.nodeType === 1 &&
|
||||
if (node.nodeType === 1 &&
|
||||
(node.classList.contains('popup') || node.classList.contains('modal') || node.classList.contains('menu')) &&
|
||||
!node.closest('.js-swimlanes') &&
|
||||
!node.closest('.js-swimlanes') &&
|
||||
!node.closest('.swimlane') &&
|
||||
!node.closest('.list') &&
|
||||
!node.closest('.minicard')) {
|
||||
|
|
@ -391,23 +390,24 @@ BlazeComponent.extendComponent({
|
|||
helper(evt, item) {
|
||||
const helper = $(`<div class="swimlane"
|
||||
style="flex-direction: column;
|
||||
height: ${swimlaneWhileSortingHeight}px;
|
||||
width: $(boardComponent.width)px;
|
||||
overflow: hidden;"/>`);
|
||||
max-height: 30vh;
|
||||
width: 100vw;
|
||||
overflow: hidden; z-index: 100;"/>`);
|
||||
helper.append(item.clone());
|
||||
// Also grab the list of lists of cards
|
||||
const list = item.next();
|
||||
helper.append(list.clone());
|
||||
return helper;
|
||||
},
|
||||
items: '.swimlane:not(.placeholder)',
|
||||
items: '.swimlane-container',
|
||||
placeholder: 'swimlane placeholder',
|
||||
distance: 7,
|
||||
start(evt, ui) {
|
||||
const listDom = ui.placeholder.next('.js-swimlane');
|
||||
const parentOffset = ui.item.parent().offset();
|
||||
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
height = ui.helper.height();
|
||||
ui.placeholder[0].setAttribute('style', `height: ${height}px !important;`);
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
listDom.addClass('moving-swimlane');
|
||||
boardComponent.setIsDragging(true);
|
||||
|
|
@ -415,40 +415,19 @@ BlazeComponent.extendComponent({
|
|||
ui.placeholder.insertAfter(ui.placeholder.next());
|
||||
boardComponent.origPlaceholderIndex = ui.placeholder.index();
|
||||
|
||||
// resize all swimlanes + headers to be a total of 150 px per row
|
||||
// this could be achieved by setIsDragging(true) but we want immediate
|
||||
// result
|
||||
ui.item
|
||||
.siblings('.js-swimlane')
|
||||
.css('height', `${swimlaneWhileSortingHeight - 26}px`);
|
||||
|
||||
// set the new scroll height after the resize and insertion of
|
||||
// the placeholder. We want the element under the cursor to stay
|
||||
// at the same place on the screen
|
||||
ui.item.parent().get(0).scrollTop =
|
||||
ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
|
||||
},
|
||||
beforeStop(evt, ui) {
|
||||
const parentOffset = ui.item.parent().offset();
|
||||
const siblings = ui.item.siblings('.js-swimlane');
|
||||
siblings.css('height', '');
|
||||
|
||||
// compute the new scroll height after the resize and removal of
|
||||
// the placeholder
|
||||
const scrollTop =
|
||||
ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
|
||||
|
||||
// then reset the original view of the swimlane
|
||||
siblings.removeClass('moving-swimlane');
|
||||
|
||||
// and apply the computed scrollheight
|
||||
ui.item.parent().get(0).scrollTop = scrollTop;
|
||||
},
|
||||
stop(evt, ui) {
|
||||
// To attribute the new index number, we need to get the DOM element
|
||||
// of the previous and the following card -- if any.
|
||||
const prevSwimlaneDom = ui.item.prevAll('.js-swimlane').get(0);
|
||||
const nextSwimlaneDom = ui.item.nextAll('.js-swimlane').get(0);
|
||||
const prevSwimlaneDom = ui.item.prevAll('.swimlane-container').get(0);
|
||||
const nextSwimlaneDom = ui.item.nextAll('.swimlane-container').get(0);
|
||||
const sortIndex = calculateIndex(prevSwimlaneDom, nextSwimlaneDom, 1);
|
||||
|
||||
$swimlanesDom.sortable('cancel');
|
||||
|
|
@ -464,39 +443,7 @@ BlazeComponent.extendComponent({
|
|||
boardComponent.setIsDragging(false);
|
||||
},
|
||||
sort(evt, ui) {
|
||||
// get the mouse position in the sortable
|
||||
const parentOffset = ui.item.parent().offset();
|
||||
const cursorY =
|
||||
evt.pageY - parentOffset.top + ui.item.parent().scrollTop();
|
||||
|
||||
// compute the intended index of the placeholder (we need to skip the
|
||||
// slots between the headers and the list of cards)
|
||||
const newplaceholderIndex = Math.floor(
|
||||
cursorY / swimlaneWhileSortingHeight,
|
||||
);
|
||||
let destPlaceholderIndex = (newplaceholderIndex + 1) * 2;
|
||||
|
||||
// if we are scrolling far away from the bottom of the list
|
||||
if (destPlaceholderIndex >= ui.item.parent().get(0).childElementCount) {
|
||||
destPlaceholderIndex = ui.item.parent().get(0).childElementCount - 1;
|
||||
}
|
||||
|
||||
// update the placeholder position in the DOM tree
|
||||
if (destPlaceholderIndex !== ui.placeholder.index()) {
|
||||
if (destPlaceholderIndex < boardComponent.origPlaceholderIndex) {
|
||||
ui.placeholder.insertBefore(
|
||||
ui.placeholder
|
||||
.siblings()
|
||||
.slice(destPlaceholderIndex - 2, destPlaceholderIndex - 1),
|
||||
);
|
||||
} else {
|
||||
ui.placeholder.insertAfter(
|
||||
ui.placeholder
|
||||
.siblings()
|
||||
.slice(destPlaceholderIndex - 1, destPlaceholderIndex),
|
||||
);
|
||||
}
|
||||
}
|
||||
Utils.scrollIfNeeded(evt);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -505,10 +452,10 @@ BlazeComponent.extendComponent({
|
|||
dragscroll.reset();
|
||||
|
||||
if ($swimlanesDom.data('uiSortable') || $swimlanesDom.data('sortable')) {
|
||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
if (Utils.isMiniScreen()) {
|
||||
$swimlanesDom.sortable('option', 'handle', '.js-swimlane-header-handle');
|
||||
} else {
|
||||
$swimlanesDom.sortable('option', 'handle', '.swimlane-header');
|
||||
$swimlanesDom.sortable('option', 'handle', '.swimlane-header-wrap');
|
||||
}
|
||||
|
||||
// Disable drag-dropping if the current user is not a board member
|
||||
|
|
@ -540,57 +487,57 @@ BlazeComponent.extendComponent({
|
|||
isViewSwimlanes() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
let boardView;
|
||||
|
||||
|
||||
if (currentUser) {
|
||||
boardView = (currentUser.profile || {}).boardView;
|
||||
} else {
|
||||
boardView = window.localStorage.getItem('boardView');
|
||||
}
|
||||
|
||||
|
||||
// If no board view is set, default to swimlanes
|
||||
if (!boardView) {
|
||||
boardView = 'board-view-swimlanes';
|
||||
}
|
||||
|
||||
|
||||
return boardView === 'board-view-swimlanes';
|
||||
},
|
||||
|
||||
isViewLists() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
let boardView;
|
||||
|
||||
|
||||
if (currentUser) {
|
||||
boardView = (currentUser.profile || {}).boardView;
|
||||
} else {
|
||||
boardView = window.localStorage.getItem('boardView');
|
||||
}
|
||||
|
||||
|
||||
return boardView === 'board-view-lists';
|
||||
},
|
||||
|
||||
isViewCalendar() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
let boardView;
|
||||
|
||||
|
||||
if (currentUser) {
|
||||
boardView = (currentUser.profile || {}).boardView;
|
||||
} else {
|
||||
boardView = window.localStorage.getItem('boardView');
|
||||
}
|
||||
|
||||
|
||||
return boardView === 'board-view-cal';
|
||||
},
|
||||
|
||||
isViewGantt() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
let boardView;
|
||||
|
||||
|
||||
if (currentUser) {
|
||||
boardView = (currentUser.profile || {}).boardView;
|
||||
} else {
|
||||
boardView = window.localStorage.getItem('boardView');
|
||||
}
|
||||
|
||||
|
||||
return boardView === 'board-view-gantt';
|
||||
},
|
||||
|
||||
|
|
@ -602,7 +549,7 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const swimlanes = currentBoard.swimlanes();
|
||||
const hasSwimlanes = swimlanes && swimlanes.length > 0;
|
||||
|
|
@ -638,7 +585,7 @@ BlazeComponent.extendComponent({
|
|||
const isBoardReady = this.isBoardReady.get();
|
||||
const isConverting = this.isConverting.get();
|
||||
const boardView = Utils.boardView();
|
||||
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('=== BOARD DEBUG STATE ===');
|
||||
console.log('currentBoardId:', currentBoardId);
|
||||
|
|
@ -648,7 +595,7 @@ BlazeComponent.extendComponent({
|
|||
console.log('boardView:', boardView);
|
||||
console.log('========================');
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
currentBoardId,
|
||||
hasCurrentBoard: !!currentBoard,
|
||||
|
|
@ -1025,4 +972,3 @@ BlazeComponent.extendComponent({
|
|||
* Gantt View Component
|
||||
* Displays cards as a Gantt chart with start/due dates
|
||||
*/
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -22,918 +22,90 @@
|
|||
padding: 0.7vh 0.7vw;
|
||||
}
|
||||
|
||||
/* Zoom and Mobile Mode Controls */
|
||||
.board-header-btns.center {
|
||||
.board-header {
|
||||
display: grid;
|
||||
flex: 1;
|
||||
gap: 0.3lh;
|
||||
}
|
||||
|
||||
body {
|
||||
&.mobile-mode {
|
||||
.board-header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
&:not(.mobile-mode) {
|
||||
.header-board-menu {
|
||||
flex: 1;
|
||||
}
|
||||
.board-header {
|
||||
justify-content: space-between;
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
.board-header-btns-left {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
.board-header-btns-right {
|
||||
flex-grow: 0;
|
||||
justify-content: end;
|
||||
}
|
||||
& .board-header-btns-right,
|
||||
& .board-header-btns-left,
|
||||
& .header-board-menu {
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 1.5ch;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Make some space on intermediate layouts */
|
||||
@media screen and (max-width: 1200px) {
|
||||
.board-header-btns-right span {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.header-board-menu, .board-header-btns {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
gap: 1ch;
|
||||
& p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
.board-header-btns-right > a {
|
||||
flex-wrap: no-wrap;
|
||||
}
|
||||
body.mobile-mode {
|
||||
header-board-menu h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
.board-header-btn {
|
||||
/* avoid wrapping if possible, at the cost of little icons */
|
||||
font-size: 0.5em;
|
||||
/* no much choice because the way FA icons are inserted */
|
||||
padding-top: 0.1lh;
|
||||
min-height: 0.8lh;
|
||||
}
|
||||
.board-header-btns-right {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: repeat(auto-fit, 1fr);
|
||||
flex: 1;
|
||||
gap: 1ch;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.board-header-btns-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5vw;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.5vh 1vw;
|
||||
border-radius: 0.5vw;
|
||||
box-shadow: 0 0.2vh 0.5vh rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn {
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
border-radius: 0.3vw !important;
|
||||
background: #fff !important;
|
||||
border: 1px solid #000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
color: #000 !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
float: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn i {
|
||||
color: #000 !important;
|
||||
float: none !important;
|
||||
display: inline !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn:hover {
|
||||
background: #000 !important;
|
||||
border-color: #000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn:hover i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn.is-active {
|
||||
background: #0079bf;
|
||||
color: white;
|
||||
border-color: #005a8a;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn.is-active i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
min-width: 3vw;
|
||||
text-align: center;
|
||||
font-size: clamp(12px, 2vw, 14px);
|
||||
cursor: pointer;
|
||||
padding: 0.3vh 0.5vw;
|
||||
border-radius: 0.3vw;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.zoom-level:hover {
|
||||
background: #f0f0f0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Mobile Mode Styles */
|
||||
.mobile-mode .board-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mobile-mode .board-canvas {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard {
|
||||
font-size: clamp(16px, 4vw, 20px);
|
||||
padding: 1.2vh 1.5vw 0.5vh;
|
||||
min-height: 3vh;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header-name {
|
||||
font-size: clamp(18px, 4.5vw, 24px);
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn {
|
||||
padding: 1vh 1.5vw;
|
||||
font-size: clamp(14px, 3.5vw, 18px);
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls {
|
||||
padding: 1vh 1.5vw;
|
||||
gap: 1vw;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn {
|
||||
padding: 1vh 1.5vw !important;
|
||||
font-size: clamp(14px, 3.5vw, 18px) !important;
|
||||
background: #fff !important;
|
||||
border: 1px solid #000 !important;
|
||||
color: #000 !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
float: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn i {
|
||||
color: #000 !important;
|
||||
float: none !important;
|
||||
display: inline !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn:hover {
|
||||
background: #000 !important;
|
||||
border-color: #000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn:hover i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-level {
|
||||
font-size: clamp(14px, 3.5vw, 18px);
|
||||
min-width: 4vw;
|
||||
}
|
||||
|
||||
/* Comprehensive Mobile Mode Styles - Works on all screen sizes */
|
||||
.mobile-mode .board-wrapper {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
transform: none !important;
|
||||
transform-origin: initial !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-canvas {
|
||||
height: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin-bottom: 2rem !important;
|
||||
display: block !important;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane-header {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
padding: 1rem !important;
|
||||
margin-bottom: 1rem !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: block !important;
|
||||
float: none !important;
|
||||
margin-bottom: 2rem !important;
|
||||
border-left: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 1rem !important;
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
display: grid !important;
|
||||
grid-template-columns: 30px 1fr auto auto !important;
|
||||
gap: 10px !important;
|
||||
align-items: center !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .list-header-name {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
grid-row: 1 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: end !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .cardCount {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
grid-row: 2 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: start !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .list-header-menu-icon {
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 3 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(24px, 3vw, 48px) !important;
|
||||
text-align: center !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .list-header-handle {
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(28px, 3.5vw, 56px) !important;
|
||||
text-align: center !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-body {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 1rem !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
font-size: clamp(16px, 2vw, 24px) !important;
|
||||
padding: 1.2vh 1.5vw 0.5vh !important;
|
||||
min-height: 3vh !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
display: block !important;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard .minicard-title {
|
||||
font-size: clamp(16px, 2vw, 24px) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard .minicard-members {
|
||||
font-size: clamp(12px, 1.5vw, 18px) !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard .minicard-lists {
|
||||
font-size: clamp(12px, 1.5vw, 18px) !important;
|
||||
}
|
||||
|
||||
/* Desktop Mode Styles */
|
||||
.desktop-mode .board-wrapper {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .swimlane {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
display: flex !important;
|
||||
float: left !important;
|
||||
margin-bottom: 0 !important;
|
||||
border-left: 1px solid #ccc !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 2.5vh 1.5vw 0.5vh !important;
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .list-header-name {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
display: inline !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
align-self: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .cardCount {
|
||||
font-size: 12px !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
align-self: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .list-header-menu-icon {
|
||||
position: absolute !important;
|
||||
right: 60px !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
padding: 14px !important;
|
||||
font-size: 40px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .list-header-handle {
|
||||
position: absolute !important;
|
||||
right: 10px !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
padding: 7px !important;
|
||||
font-size: clamp(16px, 3vw, 20px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-body {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 5px 11px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
min-height: auto !important;
|
||||
margin-bottom: 9px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard .minicard-title {
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard .minicard-members {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard .minicard-lists {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
/* Additional Mobile Mode Styles for Other Elements - Works on all screen sizes */
|
||||
.mobile-mode .swimlane-header .swimlane-title {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane-header .swimlane-description {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
padding: 1rem !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-title {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-description {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
padding: 1vh 1.5vw !important;
|
||||
display: inline-block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
display: inline !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* Force mobile mode visibility on all screen sizes */
|
||||
.mobile-mode .list-header .fa-angle-right,
|
||||
.mobile-mode .list-header .fa-arrows {
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .fa-angle-right {
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 3 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(24px, 3vw, 48px) !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .fa-arrows {
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(28px, 3.5vw, 56px) !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* Override any media queries that might hide elements in mobile mode */
|
||||
.mobile-mode * {
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list,
|
||||
.mobile-mode .swimlane,
|
||||
.mobile-mode .board-wrapper,
|
||||
.mobile-mode .board-canvas {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Force mobile mode list styling on all screen sizes - override desktop CSS */
|
||||
.mobile-mode .board-canvas {
|
||||
display: block !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane {
|
||||
display: block !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane .swimlane-header {
|
||||
display: block !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
margin: 0 0 1rem 0 !important;
|
||||
padding: 1rem !important;
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane .lists {
|
||||
display: block !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list {
|
||||
display: block !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
position: static !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list:first-child {
|
||||
margin-left: 0 !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list:last-child {
|
||||
margin-right: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list.ui-sortable-helper {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
height: auto !important;
|
||||
min-height: 60px !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list.placeholder {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
height: auto !important;
|
||||
min-height: 60px !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
}
|
||||
|
||||
/* Override any existing responsive CSS that might interfere with mobile mode */
|
||||
.mobile-mode .board-canvas .swimlane .lists {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-canvas .swimlane .lists .list {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
position: static !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Force mobile mode to override any media query styles */
|
||||
@media screen and (min-width: 801px) {
|
||||
.mobile-mode .board-canvas {
|
||||
display: block !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
min-width: 100vw !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane .lists {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
position: static !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide desktop-only elements in mobile mode (like mobile media queries do) */
|
||||
.mobile-mode .board-header-btn i.fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn .fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn .fa + .board-header-btn-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn .fa + .board-header-btn-label {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only icons in mobile mode */
|
||||
.mobile-mode .board-header-btn {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 8px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn i {
|
||||
display: inline-block !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Hide desktop-specific elements that shouldn't show in mobile mode */
|
||||
.mobile-mode .desktop-only,
|
||||
.mobile-mode .board-header .desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide desktop-specific board header buttons in mobile mode */
|
||||
.mobile-mode .board-header-btns.left {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.center {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only the right section buttons in mobile mode, but hide text labels */
|
||||
.mobile-mode .board-header-btns.right {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn .fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn .fa + .board-header-btn-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn .fa + .board-header-btn-label {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide specific desktop-only buttons that shouldn't show in mobile mode */
|
||||
.mobile-mode .board-header-btn.js-star-board span,
|
||||
.mobile-mode .board-header-btn.js-change-visibility span,
|
||||
.mobile-mode .board-header-btn.js-watch-board span,
|
||||
.mobile-mode .board-header-btn.js-sort-cards span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only icons for mobile mode buttons */
|
||||
.mobile-mode .board-header-btns.right .board-header-btn {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 8px !important;
|
||||
text-align: center !important;
|
||||
margin: 0 2px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn i {
|
||||
display: inline-block !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure mobile mode looks like small screen mobile view */
|
||||
.mobile-mode .board-header {
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btns {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn {
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i.fa {
|
||||
line-height: 32px !important;
|
||||
}
|
||||
|
||||
/* Copy mobile media query styles to mobile mode for consistent appearance */
|
||||
.mobile-mode .board-header {
|
||||
height: 40px !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btns {
|
||||
margin-top: 0px !important;
|
||||
height: 40px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: flex-end !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn {
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
font-size: 15px !important;
|
||||
margin: 0 2px !important;
|
||||
padding: 4px 8px !important;
|
||||
border-radius: 4px !important;
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||
color: #fff !important;
|
||||
text-decoration: none !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
min-width: 32px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
border-color: rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i.fa {
|
||||
line-height: 32px !important;
|
||||
font-size: 15px !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i.fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide the board title in mobile mode to match mobile view */
|
||||
.mobile-mode .header-board-menu {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Ensure the board header takes full width in mobile mode */
|
||||
.mobile-mode .board-header {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: space-between !important;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
/* Additional Desktop Mode Styles for Other Elements */
|
||||
.desktop-mode .swimlane-header .swimlane-title {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .swimlane-header .swimlane-description {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
padding: 2.5vh 1.5vw 0.5vh !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-title {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-description {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-btn {
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-btn i {
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
flex: 1;
|
||||
gap: 2ch;
|
||||
padding: 0 0.5ch;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,67 @@
|
|||
template(name="boardHeaderBar")
|
||||
h1.header-board-menu
|
||||
with currentBoard
|
||||
if $eq title 'Templates'
|
||||
| {{_ 'templates'}}
|
||||
else
|
||||
+viewer
|
||||
= title
|
||||
.board-header
|
||||
.header-board-menu
|
||||
with currentBoard
|
||||
if $eq title 'Templates'
|
||||
| {{_ 'templates'}}
|
||||
else
|
||||
+viewer
|
||||
= title
|
||||
if currentBoard
|
||||
if currentUser
|
||||
with currentBoard
|
||||
if currentUser.isBoardAdmin
|
||||
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
|
||||
unless isMiniScreen
|
||||
.board-header-btns-left
|
||||
if currentBoard
|
||||
if currentUser
|
||||
with currentBoard
|
||||
a.board-header-btn(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
span {{_ currentBoard.permission}}
|
||||
|
||||
.board-header-btns.left
|
||||
unless isMiniScreen
|
||||
a.board-header-btn.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
if $eq watchLevel "watching"
|
||||
i.fa.fa-eye
|
||||
if $eq watchLevel "tracking"
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
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}}")
|
||||
if showStarCounter
|
||||
span.board-star-counter {{currentBoard.stars}}
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
|
||||
i.fa.fa-sort
|
||||
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
|
||||
if isSortActive
|
||||
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
|
||||
.board-header-btns-right
|
||||
.separator
|
||||
if currentBoard
|
||||
if currentUser
|
||||
with currentBoard
|
||||
if currentUser.isBoardAdmin
|
||||
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(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
span {{_ currentBoard.permission}}
|
||||
a.board-header-btn(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
|
||||
a.board-header-btn.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
|
|
@ -29,86 +70,43 @@ template(name="boardHeaderBar")
|
|||
if $eq watchLevel "tracking"
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
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}}")
|
||||
if showStarCounter
|
||||
span.board-star-counter {{currentBoard.stars}}
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
|
||||
i.fa.fa-sort
|
||||
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
|
||||
if isSortActive
|
||||
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
|
||||
.board-header-btns.center
|
||||
|
||||
.board-header-btns.right
|
||||
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(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
|
||||
a.board-header-btn.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
if $eq watchLevel "watching"
|
||||
i.fa.fa-eye
|
||||
if $eq watchLevel "tracking"
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
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}}")
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
|
||||
i.fa.fa-sort
|
||||
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
|
||||
if isSortActive
|
||||
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
|
||||
if isSandstorm
|
||||
if currentUser
|
||||
a.board-header-btn.js-open-archived-board
|
||||
i.fa.fa-archive
|
||||
if isSandstorm
|
||||
if currentUser
|
||||
a.js-open-archived-board
|
||||
i.fa.fa-archive
|
||||
|
||||
//if showSort
|
||||
// a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
|
||||
// i.fa(class="{{directionClass}}")
|
||||
// span {{_ 'sort'}}{{_ listSortShortDesc}}
|
||||
//if showSort
|
||||
// a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
|
||||
// i.fa(class="{{directionClass}}")
|
||||
// span {{_ 'sort'}}{{_ listSortShortDesc}}
|
||||
|
||||
a.board-header-btn.js-open-filter-view(
|
||||
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
|
||||
class="{{#if Filter.isActive}}js-filter-active{{/if}}")
|
||||
i.fa.fa-filter
|
||||
span {{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}
|
||||
if Filter.isActive
|
||||
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
|
||||
i.fa.fa-times-thin
|
||||
a.board-header-btn.js-open-filter-view(
|
||||
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
|
||||
class="{{#if Filter.isActive}}js-filter-active{{/if}}")
|
||||
i.fa.fa-filter
|
||||
span {{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}
|
||||
if Filter.isActive
|
||||
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
|
||||
i.fa.fa-search
|
||||
span {{_ 'search'}}
|
||||
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
|
||||
i.fa.fa-search
|
||||
span {{_ 'search'}}
|
||||
|
||||
unless currentBoard.isTemplatesBoard
|
||||
a.board-header-btn.js-toggle-board-view
|
||||
|
|
@ -140,9 +138,9 @@ template(name="boardHeaderBar")
|
|||
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
.separator
|
||||
a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}")
|
||||
i.fa.fa-bars
|
||||
.separator
|
||||
a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}")
|
||||
i.fa.fa-bars
|
||||
|
||||
template(name="boardVisibilityList")
|
||||
ul.pop-over-list
|
||||
|
|
@ -172,26 +170,29 @@ template(name="boardChangeWatchPopup")
|
|||
li
|
||||
with "watching"
|
||||
a.js-select-watch
|
||||
i.fa.fa-eye
|
||||
| {{_ 'watching'}}
|
||||
if watchCheck
|
||||
i.fa.fa-check
|
||||
span
|
||||
i.fa.fa-eye
|
||||
| {{_ 'watching'}}
|
||||
if watchCheck
|
||||
i.fa.fa-check
|
||||
span.sub-name {{_ 'watching-info'}}
|
||||
li
|
||||
with "tracking"
|
||||
a.js-select-watch
|
||||
i.fa.fa-bell
|
||||
| {{_ 'tracking'}}
|
||||
if watchCheck
|
||||
i.fa.fa-check
|
||||
span
|
||||
i.fa.fa-bell
|
||||
| {{_ 'tracking'}}
|
||||
if watchCheck
|
||||
i.fa.fa-check
|
||||
span.sub-name {{_ 'tracking-info'}}
|
||||
li
|
||||
with "muted"
|
||||
a.js-select-watch
|
||||
i.fa.fa-bell-slash
|
||||
| {{_ 'muted'}}
|
||||
if watchCheck
|
||||
i.fa.fa-check
|
||||
span
|
||||
i.fa.fa-bell-slash
|
||||
| {{_ 'muted'}}
|
||||
if watchCheck
|
||||
i.fa.fa-check
|
||||
span.sub-name {{_ 'muted-info'}}
|
||||
|
||||
template(name="boardChangeViewPopup")
|
||||
|
|
@ -247,12 +248,13 @@ template(name="createBoard")
|
|||
.materialCheckBox#add-template-container
|
||||
span {{_ 'add-template-container'}}
|
||||
input.primary.wide(type="submit" value="{{_ 'create'}}")
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-import-board {{_ 'import'}}
|
||||
span.quiet
|
||||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
.create-element-foooter
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-import-board {{_ 'import'}}
|
||||
span.quiet
|
||||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
|
||||
template(name="createBoardPopup")
|
||||
form
|
||||
|
|
@ -276,12 +278,13 @@ template(name="createBoardPopup")
|
|||
.materialCheckBox#add-template-container
|
||||
span {{_ 'add-template-container'}}
|
||||
input.primary.wide(type="submit" value="{{_ 'create'}}")
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-import-board {{_ 'import'}}
|
||||
span.quiet
|
||||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
.create-element-foooter
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-import-board {{_ 'import'}}
|
||||
span.quiet
|
||||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
|
||||
// New popup for Template Container creation; shares the same form content
|
||||
template(name="createTemplateContainerPopup")
|
||||
|
|
@ -305,13 +308,14 @@ template(name="createTemplateContainerPopup")
|
|||
a.flex.js-toggle-add-template-container
|
||||
.materialCheckBox#add-template-container
|
||||
span {{_ 'add-template-container'}}
|
||||
input.primary.wide(type="submit" value="{{_ 'create'}}")
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-import-board {{_ 'import'}}
|
||||
span.quiet
|
||||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
.create-element-foooter
|
||||
input.primary.wide(type="submit" value="{{_ 'create'}}")
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-import-board {{_ 'import'}}
|
||||
span.quiet
|
||||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
|
||||
//template(name="listsortPopup")
|
||||
// h2
|
||||
|
|
@ -362,4 +366,3 @@ template(name="cardsSortPopup")
|
|||
a.js-sort-created-asc
|
||||
i.fa.fa-arrow-up
|
||||
| {{_ 'created-at-oldest-first'}}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,6 @@ BlazeComponent.extendComponent({
|
|||
const currentBoard = Utils.getCurrentBoard();
|
||||
return currentBoard && currentBoard.getWatchLevel(Meteor.userId());
|
||||
},
|
||||
|
||||
|
||||
|
||||
isStarred() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
|
|
@ -182,7 +179,7 @@ Template.boardHeaderBar.helpers({
|
|||
if (!sortBy) {
|
||||
return '🃏'; // Card icon when nothing is selected
|
||||
}
|
||||
|
||||
|
||||
// Determine which sort option is active based on sortBy object
|
||||
if (sortBy.dueAt) {
|
||||
return '📅'; // Due date icon
|
||||
|
|
@ -191,7 +188,7 @@ Template.boardHeaderBar.helpers({
|
|||
} else if (sortBy.createdAt) {
|
||||
return sortBy.createdAt === 1 ? '⬆️' : '⬇️'; // Up/down arrow based on direction
|
||||
}
|
||||
|
||||
|
||||
return '🃏'; // Default card icon
|
||||
},
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -67,81 +67,71 @@ template(name="boardList")
|
|||
// Right boards grid
|
||||
.boards-right-grid
|
||||
.boards-path-header
|
||||
.path-left
|
||||
span.path-icon.emoji-icon {{currentMenuPath.icon}}
|
||||
span.path-text {{currentMenuPath.text}}
|
||||
if BoardMultiSelection.isActive
|
||||
span.multiselection-hint
|
||||
span.emoji-icon
|
||||
i.fa.fa-thumb-tack
|
||||
| {{_ 'multi-selection-active'}}
|
||||
.path-right
|
||||
if canModifyBoards
|
||||
if hasBoardsSelected
|
||||
button.js-archive-selected-boards.board-header-btn
|
||||
.path-bottom
|
||||
.path-left
|
||||
span.path-icon.emoji-icon {{currentMenuPath.icon}}
|
||||
span.path-text {{currentMenuPath.text}}
|
||||
.path-right
|
||||
unless isMiniScreen
|
||||
+headerMultiSelection
|
||||
if canModifyBoards
|
||||
a.board-header-btn.js-multiselection-activate(
|
||||
title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
|
||||
class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archive-board'}}
|
||||
button.js-duplicate-selected-boards.board-header-btn
|
||||
span.emoji-icon
|
||||
i.fa.fa-clipboard
|
||||
span {{_ 'duplicate-board'}}
|
||||
a.board-header-btn.js-multiselection-activate(
|
||||
title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
|
||||
class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-check-square-o
|
||||
if BoardMultiSelection.isActive
|
||||
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-times
|
||||
i.fa.fa-check-square-o
|
||||
if BoardMultiSelection.isActive
|
||||
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-times
|
||||
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
|
||||
li.js-add-board
|
||||
if isSelectedMenu 'templates'
|
||||
a.board-list-item.label(title="{{_ 'add-template-container'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-template-container'}}
|
||||
| {{_ 'add-template-container'}}
|
||||
else
|
||||
a.board-list-item.label(title="{{_ 'add-board'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-board'}}
|
||||
| {{_ 'add-board'}}
|
||||
each boards
|
||||
li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true")
|
||||
if isInvited
|
||||
.board-list-item
|
||||
if BoardMultiSelection.isActive
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
span.details
|
||||
span.board-list-item-name= title
|
||||
.board-card-header
|
||||
span.js-star-board(
|
||||
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
||||
title="{{_ 'star-board-title'}}")
|
||||
span.emoji-icon
|
||||
| {{#if isStarred}}⭐{{else}}☆{{/if}}
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
.board-card-body
|
||||
span.details
|
||||
span.board-list-item-name= title
|
||||
p.board-list-item-desc {{_ 'just-invited'}}
|
||||
button.js-accept-invite.primary {{_ 'accept'}}
|
||||
button.js-decline-invite {{_ 'decline'}}
|
||||
.board-card-footer
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||
class="{{#if BoardMultiSelection.isActive }}active{{/if}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
else
|
||||
if $eq type "template-container"
|
||||
.template-container.board-list-item
|
||||
if BoardMultiSelection.isActive
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
span.board-handle(title="{{_ 'drag-board'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-arrows
|
||||
|
||||
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
.template-container.board-list-item
|
||||
if BoardMultiSelection.isActive
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
|
||||
|
||||
span.details
|
||||
span.board-list-item-name(title="{{_ 'template-container'}}")
|
||||
+viewer
|
||||
= title
|
||||
p.board-list-item-desc
|
||||
+viewer
|
||||
= description
|
||||
//- #FIXME: is this obsolete ?
|
||||
//- p.board-list-item-desc
|
||||
//- +viewer
|
||||
//- = description
|
||||
if hasSpentTimeCards
|
||||
span.js-has-spenttime-cards(
|
||||
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
|
||||
|
|
@ -154,19 +144,20 @@ template(name="boardList")
|
|||
span.emoji-icon
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
else
|
||||
.board-list-item
|
||||
if BoardMultiSelection.isActive
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
span.board-handle(title="{{_ 'drag-board'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-arrows
|
||||
|
||||
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
.board-list-item
|
||||
if BoardMultiSelection.isActive
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
|
||||
|
||||
span.details
|
||||
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
|
||||
+viewer
|
||||
= title
|
||||
//- p.board-list-item-desc
|
||||
//- +viewer
|
||||
//- = description
|
||||
unless currentSetting.hideBoardMemberList
|
||||
if allowsBoardMemberList
|
||||
.minicard-members
|
||||
|
|
@ -175,34 +166,24 @@ template(name="boardList")
|
|||
+userAvatar(userId=member noRemove=true)
|
||||
unless currentSetting.hideCardCounterList
|
||||
if allowsCardCounterList
|
||||
.minicard-lists.flex.flex-wrap
|
||||
.minicard-lists
|
||||
each list in boardLists _id
|
||||
.item
|
||||
| {{ list }}
|
||||
p.board-list-item-desc
|
||||
+viewer
|
||||
= description
|
||||
if hasSpentTimeCards
|
||||
span.js-has-spenttime-cards(
|
||||
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
|
||||
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-clock-o
|
||||
a.js-star-board(
|
||||
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
||||
title="{{_ 'star-board-title'}}")
|
||||
span.emoji-icon
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
a.js-star-board(
|
||||
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
||||
title="{{_ 'star-board-title'}}")
|
||||
span.emoji-icon
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
|
||||
template(name="boardListHeaderBar")
|
||||
h1 {{_ title }}
|
||||
//.board-header-btns.right
|
||||
// a.board-header-btn.js-open-archived-board
|
||||
// i.fa.fa-archive
|
||||
// span {{_ 'archives'}}
|
||||
// a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
|
||||
// i.fa.fa-clone
|
||||
// span {{_ 'templates'}}
|
||||
|
||||
// Recursive template for workspaces tree
|
||||
template(name="workspaceTree")
|
||||
|
|
@ -214,7 +195,7 @@ template(name="workspaceTree")
|
|||
span.workspace-drag-handle
|
||||
span.emoji-icon
|
||||
i.fa.fa-arrows
|
||||
|
||||
|
||||
a.js-select-workspace(data-id="{{id}}")
|
||||
span.workspace-icon
|
||||
if icon
|
||||
|
|
@ -231,3 +212,16 @@ template(name="workspaceTree")
|
|||
a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") +
|
||||
if children
|
||||
+workspaceTree(nodes=children selectedWorkspaceId=selectedWorkspaceId)
|
||||
|
||||
template(name="headerMultiSelection")
|
||||
if BoardMultiSelection.isActive
|
||||
if canModifyBoards
|
||||
if hasBoardsSelected
|
||||
button.negate.js-archive-selected-boards.board-header-btn
|
||||
span.emoji-icon
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archive-board'}}
|
||||
button.negate.js-duplicate-selected-boards.board-header-btn
|
||||
span.emoji-icon
|
||||
i.fa.fa-clipboard
|
||||
span {{_ 'duplicate-board'}}
|
||||
|
|
@ -108,10 +108,7 @@ BlazeComponent.extendComponent({
|
|||
const newTree = EJSON.clone(tree);
|
||||
|
||||
// Remove the dragged space
|
||||
const { tree: treeAfterRemoval, removed } = removeSpace(
|
||||
newTree,
|
||||
draggedSpaceId,
|
||||
);
|
||||
const { tree: treeAfterRemoval, removed } = removeSpace(newTree, draggedSpaceId);
|
||||
|
||||
if (removed) {
|
||||
// Insert after target
|
||||
|
|
@ -127,46 +124,39 @@ BlazeComponent.extendComponent({
|
|||
onRendered() {
|
||||
// jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management
|
||||
// The old sortable code has been removed to prevent conflicts
|
||||
/* OLD SORTABLE CODE - DISABLED
|
||||
const itemsSelector = '.js-board:not(.placeholder)';
|
||||
|
||||
const $boards = this.$('.js-boards');
|
||||
$boards.sortable({
|
||||
connectWith: '.js-boards',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-list',
|
||||
helper: 'clone',
|
||||
distance: 7,
|
||||
items: itemsSelector,
|
||||
placeholder: 'board-wrapper placeholder',
|
||||
start(evt, ui) {
|
||||
ui.helper.css('z-index', 1000);
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
},
|
||||
async stop(evt, ui) {
|
||||
const prevBoardDom = ui.item.prev('.js-board').get(0);
|
||||
const nextBoardDom = ui.item.next('.js-board').get(0);
|
||||
const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1);
|
||||
// #FIXME OLD SORTABLE CODE - WILL BE DISABLED
|
||||
//
|
||||
// const itemsSelector = '.js-board';
|
||||
|
||||
const boardDomElement = ui.item.get(0);
|
||||
const board = Blaze.getData(boardDomElement);
|
||||
$boards.sortable('cancel');
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser && typeof currentUser.setBoardSortIndex === 'function') {
|
||||
await currentUser.setBoardSortIndex(board._id, sortIndex.base);
|
||||
}
|
||||
},
|
||||
});
|
||||
// const $boards = this.$('.js-boards');
|
||||
// $boards.sortable({
|
||||
// connectWith: '.js-boards',
|
||||
// tolerance: 'pointer',
|
||||
// appendTo: '.board-list',
|
||||
// helper: 'clone',
|
||||
// distance: 7,
|
||||
// items: itemsSelector,
|
||||
// placeholder: 'board-wrapper placeholder',
|
||||
// start(evt, ui) {
|
||||
// ui.helper.css('z-index', 1000);
|
||||
// ui.placeholder.height(ui.helper.height());
|
||||
// EscapeActions.executeUpTo('popup-close');
|
||||
// },
|
||||
// async stop(evt, ui) {
|
||||
// const prevBoardDom = ui.item.prev('.js-board').get(0);
|
||||
// const nextBoardDom = ui.item.next('.js-board').get(0);
|
||||
// const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1);
|
||||
|
||||
this.autorun(() => {
|
||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
$boards.sortable({
|
||||
handle: '.board-handle',
|
||||
});
|
||||
}
|
||||
});
|
||||
*/
|
||||
// const boardDomElement = ui.item.get(0);
|
||||
// const board = Blaze.getData(boardDomElement);
|
||||
// $boards.sortable('cancel');
|
||||
// const currentUser = ReactiveCache.getCurrentUser();
|
||||
// if (currentUser && typeof currentUser.setBoardSortIndex === 'function') {
|
||||
// await currentUser.setBoardSortIndex(board._id, sortIndex.base);
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
},
|
||||
userHasTeams() {
|
||||
if (ReactiveCache.getCurrentUser()?.teams?.length > 0) return true;
|
||||
|
|
@ -357,7 +347,7 @@ BlazeComponent.extendComponent({
|
|||
const lists = ReactiveCache.getLists({ 'boardId': boardId, 'archived': false },{sort: ['sort','asc']});
|
||||
const ret = lists.map(list => {
|
||||
let cardCount = ReactiveCache.getCards({ 'boardId': boardId, 'listId': list._id }).length;
|
||||
return `${list.title}: ${cardCount}`;
|
||||
return `${list.title}: ${cardCountcardCount}`;
|
||||
});
|
||||
return ret;
|
||||
*/
|
||||
|
|
@ -535,6 +525,7 @@ BlazeComponent.extendComponent({
|
|||
'click .js-multiselection-reset'(evt) {
|
||||
evt.preventDefault();
|
||||
BoardMultiSelection.disable();
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-toggle-board-multi-selection'(evt) {
|
||||
evt.preventDefault();
|
||||
|
|
@ -708,6 +699,7 @@ BlazeComponent.extendComponent({
|
|||
icon: newIcon || '📁',
|
||||
});
|
||||
|
||||
|
||||
Meteor.call('setWorkspacesTree', updatedTree, (err) => {
|
||||
if (err) console.error(err);
|
||||
});
|
||||
|
|
@ -808,6 +800,7 @@ BlazeComponent.extendComponent({
|
|||
// Get the workspace ID directly from the dropped workspace-node's data-workspace-id attribute
|
||||
const workspaceId = targetEl.getAttribute('data-workspace-id');
|
||||
|
||||
|
||||
if (workspaceId) {
|
||||
if (isMultiBoard) {
|
||||
// Multi-board drag
|
||||
|
|
@ -830,6 +823,7 @@ BlazeComponent.extendComponent({
|
|||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
|
||||
const menuType = evt.currentTarget.getAttribute('data-type');
|
||||
// Only allow drop on "remaining" menu to unassign boards from spaces
|
||||
if (menuType === 'remaining') {
|
||||
|
|
@ -844,9 +838,11 @@ BlazeComponent.extendComponent({
|
|||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
|
||||
const menuType = evt.currentTarget.getAttribute('data-type');
|
||||
evt.currentTarget.classList.remove('drag-over');
|
||||
|
||||
|
||||
// Only handle drops on "remaining" menu
|
||||
if (menuType !== 'remaining') return;
|
||||
|
||||
|
|
@ -908,6 +904,7 @@ BlazeComponent.extendComponent({
|
|||
};
|
||||
const allBoards = ReactiveCache.getBoards(query, {});
|
||||
|
||||
|
||||
if (type === 'starred') {
|
||||
return allBoards.filter(
|
||||
(b) => currentUser && currentUser.hasStarred(b._id),
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
.original-positions-content {
|
||||
background-color: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
.original-position-item {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
margin-bottom: 10px;
|
||||
padding: 12px;
|
||||
transition: all 0.2s ease;
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
|
||||
.entity-id {
|
||||
color: #6c757d;
|
||||
font-size: 11px;
|
||||
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
|
|
@ -123,12 +123,12 @@
|
|||
.original-position-description {
|
||||
color: #495057;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
|
||||
}
|
||||
|
||||
.original-title {
|
||||
color: #6c757d;
|
||||
font-size: 12px;
|
||||
|
||||
margin-bottom: 6px;
|
||||
padding: 4px 6px;
|
||||
background-color: #e9ecef;
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
|
||||
.original-position-date {
|
||||
color: #6c757d;
|
||||
font-size: 11px;
|
||||
|
||||
}
|
||||
|
||||
.no-original-positions {
|
||||
|
|
@ -152,7 +152,7 @@
|
|||
}
|
||||
|
||||
.no-original-positions i {
|
||||
font-size: 24px;
|
||||
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
color: #adb5bd;
|
||||
|
|
@ -164,32 +164,32 @@
|
|||
margin: 5px 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
.original-positions-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
.original-positions-header .btn {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.original-positions-filters .btn-group {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.original-position-item-header {
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
|
||||
.entity-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
|
||||
.original-position-item-details {
|
||||
margin-left: 0;
|
||||
margin-top: 8px;
|
||||
|
|
@ -203,60 +203,60 @@
|
|||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.original-positions-content {
|
||||
background-color: #1a202c;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
|
||||
.original-position-item {
|
||||
background-color: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.original-position-item:hover {
|
||||
background-color: #4a5568;
|
||||
border-color: #718096;
|
||||
}
|
||||
|
||||
|
||||
.original-position-item-header {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.original-position-item-header i {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
|
||||
.entity-name {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.entity-id {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
|
||||
.original-position-description {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.original-title {
|
||||
background-color: #4a5568;
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
|
||||
.original-title strong {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.original-position-date {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
|
||||
.no-original-positions {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
|
||||
.no-original-positions i {
|
||||
color: #718096;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,75 +6,80 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
.attachment-gallery {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
.attachment-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
display: grid;
|
||||
grid-template-columns: 10ch auto;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
grid-template-rows: repeat(auto-fit, minmax(1.5lh, auto));
|
||||
justify-content: stretch;
|
||||
gap: 2ch;
|
||||
padding: 2ch;
|
||||
border-radius: 0.6ch;
|
||||
}
|
||||
|
||||
.attachment-item:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.attachment-thumbnail-container {
|
||||
display: block;
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
max-height: 150px;
|
||||
padding-right: 16px;
|
||||
|
||||
.attachment-details-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.attachment-thumbnail-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.attachment-thumbnail {
|
||||
max-width: 150px;
|
||||
max-height: 150px;
|
||||
min-height: 2em;
|
||||
/* more deterministic outcome */
|
||||
aspect-ratio: 1/1;
|
||||
object-fit: cover;
|
||||
max-width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: 0.4ch;
|
||||
}
|
||||
.attachment-thumbnail-text {
|
||||
min-height: 2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2em;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.attachment-details-container {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.attachment-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-right: 25px; /* Make sure the icons are not to far to the right */
|
||||
flex: 1;
|
||||
gap: 0.5ch;
|
||||
align-items: center;
|
||||
}
|
||||
.attachment-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1.5ch;
|
||||
}
|
||||
.attachment-actions a {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.attachment-actions a:first-child {
|
||||
margin-left: 0;
|
||||
|
||||
body.mobile-mode .attachment-actions {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.add-attachment {
|
||||
border: 1px dashed #555;
|
||||
border-radius: .5ch;
|
||||
cursor: pointer;
|
||||
aspect-ratio: 1/1;
|
||||
height: 1.5lh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #555;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.icon {
|
||||
font-size: 1.5em;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.icon:hover {
|
||||
color: #666;
|
||||
|
|
@ -95,26 +100,25 @@
|
|||
height: 100%;
|
||||
}
|
||||
#viewer-top-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
font-size: 2rem;
|
||||
padding: 0.3lh 0.5ch;
|
||||
}
|
||||
#attachment-name {
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
max-width: calc(
|
||||
100% - 50px
|
||||
); /* Make sure the name does not overlap the close button */
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
#viewer-close {
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 4em;
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
top: 16px;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
/* Upload progress indicators for drag-and-drop uploads */
|
||||
|
|
@ -122,30 +126,24 @@
|
|||
.card-details-upload-progress {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
border-radius: 0.4ch;
|
||||
|
||||
}
|
||||
|
||||
.upload-progress-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.upload-progress-header i {
|
||||
margin-right: 8px;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.upload-progress-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #dee2e6;
|
||||
|
|
@ -158,22 +156,17 @@
|
|||
|
||||
.upload-progress-filename {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: #495057;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.upload-progress-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: #e9ecef;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.upload-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #007bff, #0056b3);
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 3px;
|
||||
|
|
@ -187,7 +180,6 @@
|
|||
.upload-progress-success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
|
@ -199,47 +191,6 @@
|
|||
color: #28a745;
|
||||
}
|
||||
|
||||
.upload-progress-error i,
|
||||
.upload-progress-success i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* Minicard specific styles */
|
||||
.minicard-upload-progress {
|
||||
margin: 4px 0;
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.minicard-upload-progress .upload-progress-item {
|
||||
padding: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.minicard-upload-progress .upload-progress-filename {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Card details specific styles */
|
||||
.card-details-upload-progress {
|
||||
margin: 12px 0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card-details-upload-progress .upload-progress-header {
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-details-upload-progress .upload-progress-item {
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-details-upload-progress .upload-progress-filename {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Drag over state for minicards */
|
||||
.minicard.is-dragging-over {
|
||||
border: 2px dashed #007bff !important;
|
||||
|
|
@ -256,7 +207,6 @@
|
|||
color: white;
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
margin: 0 20px;
|
||||
}
|
||||
#prev-attachment {
|
||||
font-size: 4em;
|
||||
|
|
@ -322,7 +272,6 @@
|
|||
position: absolute;
|
||||
bottom: 2.2em;
|
||||
font-size: 1.6em;
|
||||
padding: 16px;
|
||||
}
|
||||
#prev-attachment {
|
||||
left: 0;
|
||||
|
|
@ -356,19 +305,10 @@
|
|||
margin-top: 20%;
|
||||
width: 100%;
|
||||
}
|
||||
.attachment-thumbnail-container {
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
}
|
||||
.attachment-thumbnail {
|
||||
max-width: 100px;
|
||||
}
|
||||
.attachment-details {
|
||||
flex-direction: column;
|
||||
margin-right: 0px;
|
||||
}
|
||||
.attachment-actions {
|
||||
flex-direction: row;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,15 +49,11 @@ template(name="attachmentViewer")
|
|||
i.fa.fa-caret-right#next-attachment
|
||||
|
||||
template(name="attachmentGallery")
|
||||
|
||||
if canModifyCard
|
||||
a.add-attachment.js-add-attachment
|
||||
i.fa.fa-plus
|
||||
.attachment-gallery
|
||||
|
||||
if canModifyCard
|
||||
a.attachment-item.add-attachment.js-add-attachment
|
||||
i.fa.fa-plus
|
||||
|
||||
each attachments
|
||||
|
||||
.attachment-item(class="{{#if isAttachmentMigrating _id}}migrating{{/if}}")
|
||||
.attachment-thumbnail-container.open-preview(data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}")
|
||||
if link
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
.card-date {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
padding: 1px 3px;
|
||||
background-color: #dbdbdb;
|
||||
}
|
||||
|
|
@ -106,6 +105,10 @@
|
|||
background-color: #e6c200;
|
||||
}
|
||||
|
||||
.date a:has(time) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card-date.end-date {
|
||||
background-color: #ffb3b3; /* Light red for end */
|
||||
color: #000; /* Black text for end */
|
||||
|
|
@ -139,6 +142,6 @@
|
|||
}
|
||||
.customfield-date {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { TAPi18n } from '/imports/i18n';
|
||||
import { DatePicker } from '/client/lib/datepicker';
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDate,
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDate,
|
||||
formatDateByUserPreference,
|
||||
formatTime,
|
||||
getISOWeek,
|
||||
isValidDate,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isSame,
|
||||
add,
|
||||
subtract,
|
||||
startOf,
|
||||
endOf,
|
||||
format,
|
||||
parseDate,
|
||||
now,
|
||||
createDate,
|
||||
fromNow,
|
||||
formatTime,
|
||||
getISOWeek,
|
||||
isValidDate,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isSame,
|
||||
add,
|
||||
subtract,
|
||||
startOf,
|
||||
endOf,
|
||||
format,
|
||||
parseDate,
|
||||
now,
|
||||
createDate,
|
||||
fromNow,
|
||||
calendar,
|
||||
diff
|
||||
} from '/imports/lib/dateUtils';
|
||||
|
|
@ -143,7 +143,7 @@ class CardReceivedDate extends CardDate {
|
|||
const startAt = this.data().getStart();
|
||||
const theDate = this.date.get();
|
||||
const now = this.now.get();
|
||||
|
||||
|
||||
// Received date logic: if received date is after start, due, or end dates, it's overdue
|
||||
if (
|
||||
(startAt && isAfter(theDate, startAt)) ||
|
||||
|
|
@ -187,7 +187,7 @@ class CardStartDate extends CardDate {
|
|||
const endAt = this.data().getEnd();
|
||||
const theDate = this.date.get();
|
||||
const now = this.now.get();
|
||||
|
||||
|
||||
// Start date logic: if start date is after due or end dates, it's overdue
|
||||
if ((endAt && isAfter(theDate, endAt)) || (dueAt && isAfter(theDate, dueAt))) {
|
||||
classes += 'overdue';
|
||||
|
|
@ -230,7 +230,7 @@ class CardDueDate extends CardDate {
|
|||
const endAt = this.data().getEnd();
|
||||
const theDate = this.date.get();
|
||||
const now = this.now.get();
|
||||
|
||||
|
||||
// If there's an end date and it's before the due date, task is completed early
|
||||
if (endAt && isBefore(endAt, theDate)) {
|
||||
classes += 'completed-early';
|
||||
|
|
@ -242,7 +242,7 @@ class CardDueDate extends CardDate {
|
|||
// Due date logic based on current time
|
||||
else {
|
||||
const daysDiff = diff(theDate, now, 'days');
|
||||
|
||||
|
||||
if (daysDiff < 0) {
|
||||
// Due date is in the past - overdue
|
||||
classes += 'overdue';
|
||||
|
|
@ -254,7 +254,7 @@ class CardDueDate extends CardDate {
|
|||
classes += 'not-due';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +286,7 @@ class CardEndDate extends CardDate {
|
|||
let classes = 'end-date ';
|
||||
const dueAt = this.data().getDue();
|
||||
const theDate = this.date.get();
|
||||
|
||||
|
||||
if (!dueAt) {
|
||||
// No due date set - just show as completed
|
||||
classes += 'completed';
|
||||
|
|
@ -371,7 +371,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
|
|||
template() {
|
||||
return 'minicardReceivedDate';
|
||||
}
|
||||
|
||||
|
||||
showDate() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
|
||||
|
|
@ -383,7 +383,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
|
|||
template() {
|
||||
return 'minicardStartDate';
|
||||
}
|
||||
|
||||
|
||||
showDate() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
|
||||
|
|
@ -395,7 +395,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
|
|||
template() {
|
||||
return 'minicardDueDate';
|
||||
}
|
||||
|
||||
|
||||
showDate() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
|
||||
|
|
@ -407,7 +407,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
|
|||
template() {
|
||||
return 'minicardEndDate';
|
||||
}
|
||||
|
||||
|
||||
showDate() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
|
||||
|
|
@ -419,7 +419,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
|
|||
template() {
|
||||
return 'minicardCustomFieldDate';
|
||||
}
|
||||
|
||||
|
||||
showDate() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
.new-description {
|
||||
position: relative;
|
||||
margin: 0 0 20px 0;
|
||||
flex: 1;
|
||||
}
|
||||
.new-description.is-open .helper {
|
||||
display: inline-block;
|
||||
}
|
||||
.new-description.is-open textarea {
|
||||
min-height: 100px;
|
||||
.new-description textarea {
|
||||
min-height: 1lh;
|
||||
color: #4d4d4d;
|
||||
cursor: auto;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.new-description .too-long {
|
||||
margin-top: 8px;
|
||||
|
|
@ -19,9 +15,6 @@
|
|||
background-color: #fff;
|
||||
border: 0;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
|
||||
height: 36px;
|
||||
margin: 4px 4px 6px 0;
|
||||
padding: 9px 11px;
|
||||
width: 100%;
|
||||
}
|
||||
.new-description textarea:hover,
|
||||
|
|
@ -39,16 +32,12 @@
|
|||
border: 0;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
|
||||
color: #8c8c8c;
|
||||
height: 36px;
|
||||
margin: 4px 4px 6px 0;
|
||||
width: 92%;
|
||||
}
|
||||
.description-item:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.description-item.add-description {
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
}
|
||||
.description-item.add-description a {
|
||||
display: block;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -2,25 +2,25 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
|||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { DatePicker } from '/client/lib/datepicker';
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDate,
|
||||
formatTime,
|
||||
getISOWeek,
|
||||
isValidDate,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isSame,
|
||||
add,
|
||||
subtract,
|
||||
startOf,
|
||||
endOf,
|
||||
format,
|
||||
parseDate,
|
||||
now,
|
||||
createDate,
|
||||
fromNow,
|
||||
calendar
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDate,
|
||||
formatTime,
|
||||
getISOWeek,
|
||||
isValidDate,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isSame,
|
||||
add,
|
||||
subtract,
|
||||
startOf,
|
||||
endOf,
|
||||
format,
|
||||
parseDate,
|
||||
now,
|
||||
createDate,
|
||||
fromNow,
|
||||
calendar
|
||||
} from '/imports/lib/dateUtils';
|
||||
import Cards from '/models/cards';
|
||||
import Boards from '/models/boards';
|
||||
|
|
@ -35,6 +35,7 @@ import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlane
|
|||
import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
||||
import { handleFileUpload } from './attachments';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
import PopupComponent from '../main/popup';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndexData } = Utils;
|
||||
|
|
@ -60,19 +61,8 @@ BlazeComponent.extendComponent({
|
|||
onCreated() {
|
||||
this.currentBoard = Utils.getCurrentBoard();
|
||||
this.isLoaded = new ReactiveVar(false);
|
||||
this.dep = new Tracker.Dependency();
|
||||
|
||||
if (this.parentComponent() && this.parentComponent().parentComponent()) {
|
||||
const boardBody = this.parentComponent().parentComponent();
|
||||
//in Miniview parent is Board, not BoardBody.
|
||||
if (boardBody !== null) {
|
||||
// Only show overlay in mobile mode, not in desktop mode
|
||||
const isMobile = Utils.getMobileMode();
|
||||
if (isMobile) {
|
||||
boardBody.showOverlay.set(true);
|
||||
}
|
||||
boardBody.mouseHasEnterCardDetails = false;
|
||||
}
|
||||
}
|
||||
this.calculateNextPeak();
|
||||
|
||||
Meteor.subscribe('unsaved-edits');
|
||||
|
|
@ -85,6 +75,18 @@ BlazeComponent.extendComponent({
|
|||
// });
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
const boardOverlay = document.getElementsByClassName('board-overlay')?.[0];
|
||||
this.boardBody = BlazeComponent.getComponentForElement(boardOverlay);
|
||||
if (this.boardBody) {
|
||||
this.boardBody.mouseHasEnterCardDetails = false;
|
||||
}
|
||||
const isMobile = Utils.getMobileMode();
|
||||
if (isMobile && Session.get('currentCard')) {
|
||||
//this.boardBody?.showOverlay.set(true);
|
||||
}
|
||||
},
|
||||
|
||||
isWatching() {
|
||||
const card = this.currentData();
|
||||
if (!card || typeof card.findWatcher !== 'function') return false;
|
||||
|
|
@ -95,8 +97,8 @@ BlazeComponent.extendComponent({
|
|||
return ReactiveCache.getCurrentUser().hasCustomFieldsGrid();
|
||||
},
|
||||
|
||||
|
||||
cardMaximized() {
|
||||
this.dep.depend();
|
||||
return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized();
|
||||
},
|
||||
|
||||
|
|
@ -175,6 +177,11 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
onRendered() {
|
||||
// #FIXME hackish; if accepted tweak static funcs
|
||||
if (this.cardMaximized()) {
|
||||
PopupComponent.maximize({target: this.firstNode()});
|
||||
}
|
||||
|
||||
if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) {
|
||||
// Send Webhook but not create Activities records ---
|
||||
const card = this.currentData();
|
||||
|
|
@ -209,11 +216,11 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
|
||||
const $checklistsDom = this.$('.card-checklist-items');
|
||||
|
||||
const sortableSelector = Utils.isMiniScreen() ? '.checklist-handle' : '.checklist-title';
|
||||
$checklistsDom.sortable({
|
||||
tolerance: 'pointer',
|
||||
helper: 'clone',
|
||||
handle: '.checklist-title',
|
||||
handle: sortableSelector,
|
||||
items: '.js-checklist',
|
||||
placeholder: 'checklist placeholder',
|
||||
distance: 7,
|
||||
|
|
@ -282,6 +289,8 @@ BlazeComponent.extendComponent({
|
|||
return ReactiveCache.getCurrentUser()?.isBoardMember();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Disable sorting if the current user is not a board member
|
||||
this.autorun(() => {
|
||||
const disabled = !userIsMember();
|
||||
|
|
@ -289,10 +298,7 @@ BlazeComponent.extendComponent({
|
|||
$checklistsDom.data('uiSortable') ||
|
||||
$checklistsDom.data('sortable')
|
||||
) {
|
||||
$checklistsDom.sortable('option', 'disabled', disabled);
|
||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
$checklistsDom.sortable({ handle: '.checklist-handle' });
|
||||
}
|
||||
$checklistsDom.sortable('option', 'handle', sortableSelector);
|
||||
}
|
||||
if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) {
|
||||
$subtasksDom.sortable('option', 'disabled', disabled);
|
||||
|
|
@ -301,11 +307,7 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
onDestroyed() {
|
||||
if (this.parentComponent() === null) return;
|
||||
const parentComponent = this.parentComponent().parentComponent();
|
||||
//on mobile view parent is Board, not board body.
|
||||
if (parentComponent === null) return;
|
||||
parentComponent.showOverlay.set(false);
|
||||
this.boardBody?.showOverlay.set(false);
|
||||
},
|
||||
|
||||
events() {
|
||||
|
|
@ -332,59 +334,11 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
'mousedown .js-card-drag-handle'(event) {
|
||||
event.preventDefault();
|
||||
const $card = $(event.target).closest('.card-details');
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const startLeft = $card.offset().left;
|
||||
const startTop = $card.offset().top;
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
const deltaX = e.clientX - startX;
|
||||
const deltaY = e.clientY - startY;
|
||||
$card.css({
|
||||
left: startLeft + deltaX + 'px',
|
||||
top: startTop + deltaY + 'px'
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
$(document).off('mousemove', onMouseMove);
|
||||
$(document).off('mouseup', onMouseUp);
|
||||
};
|
||||
|
||||
$(document).on('mousemove', onMouseMove);
|
||||
$(document).on('mouseup', onMouseUp);
|
||||
PopupComponent.toFront(event);
|
||||
},
|
||||
'mousedown .js-card-title-drag-handle'(event) {
|
||||
// Allow dragging from title for ReadOnly users
|
||||
// Don't interfere with text selection
|
||||
if (event.target.tagName === 'A' || $(event.target).closest('a').length > 0) {
|
||||
return; // Don't drag if clicking on links
|
||||
}
|
||||
|
||||
'click .js-card-send-to-back'(event) {
|
||||
event.preventDefault();
|
||||
const $card = $(event.target).closest('.card-details');
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const startLeft = $card.offset().left;
|
||||
const startTop = $card.offset().top;
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
const deltaX = e.clientX - startX;
|
||||
const deltaY = e.clientY - startY;
|
||||
$card.css({
|
||||
left: startLeft + deltaX + 'px',
|
||||
top: startTop + deltaY + 'px'
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
$(document).off('mousemove', onMouseMove);
|
||||
$(document).off('mouseup', onMouseUp);
|
||||
};
|
||||
|
||||
$(document).on('mousemove', onMouseMove);
|
||||
$(document).on('mouseup', onMouseUp);
|
||||
PopupComponent.toBack(event);
|
||||
},
|
||||
'click .js-close-card-details'() {
|
||||
// Get board ID from either the card data or current board in session
|
||||
|
|
@ -392,26 +346,21 @@ BlazeComponent.extendComponent({
|
|||
const boardId = (card && card.boardId) || Utils.getCurrentBoard()._id;
|
||||
const cardId = card && card._id;
|
||||
|
||||
if (boardId) {
|
||||
// In desktop mode, remove from openCards array
|
||||
const isMobile = Utils.getMobileMode();
|
||||
if (!isMobile && cardId) {
|
||||
const openCards = Session.get('openCards') || [];
|
||||
const filtered = openCards.filter(id => id !== cardId);
|
||||
Session.set('openCards', filtered);
|
||||
|
||||
// If this was the current card, clear it
|
||||
if (Session.get('currentCard') === cardId) {
|
||||
Session.set('currentCard', null);
|
||||
}
|
||||
// Don't navigate away in desktop mode - just close the card
|
||||
return;
|
||||
if (boardId && cardId) {
|
||||
const openCards = Session.get('openCards') || [];
|
||||
const filtered = openCards.filter(id => id !== cardId);
|
||||
// If this was the current card, clear it
|
||||
if (openCards.length === filtered.length) {
|
||||
Session.set('currentCard', null);
|
||||
}
|
||||
else {
|
||||
Session.set('currentCard', filtered[0]);
|
||||
}
|
||||
Session.set('openCards', filtered);
|
||||
|
||||
// Mobile mode: Clear the current card session to close the card
|
||||
Session.set('currentCard', null);
|
||||
|
||||
// Navigate back to board without card
|
||||
// Navigate back to board without card: must be done at the time of writing
|
||||
// otherwise the route for the card is disabled until another
|
||||
// card is opened
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
if (board) {
|
||||
FlowRouter.go('board', {
|
||||
|
|
@ -434,34 +383,6 @@ BlazeComponent.extendComponent({
|
|||
Meteor.call('changeDateFormat', dateFormat);
|
||||
},
|
||||
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
|
||||
// Mobile: switch to desktop popup view (maximize)
|
||||
'click .js-mobile-switch-to-desktop'(event) {
|
||||
event.preventDefault();
|
||||
// Switch global mode to desktop so the card appears as desktop popup
|
||||
Utils.setMobileMode(false);
|
||||
},
|
||||
'click .js-card-zoom-in'(event) {
|
||||
event.preventDefault();
|
||||
const current = Utils.getCardZoom();
|
||||
const newZoom = Math.min(3.0, current + 0.1);
|
||||
Utils.setCardZoom(newZoom);
|
||||
},
|
||||
'click .js-card-zoom-out'(event) {
|
||||
event.preventDefault();
|
||||
const current = Utils.getCardZoom();
|
||||
const newZoom = Math.max(0.5, current - 0.1);
|
||||
Utils.setCardZoom(newZoom);
|
||||
},
|
||||
'click .js-card-mobile-desktop-toggle'(event) {
|
||||
event.preventDefault();
|
||||
const currentMode = Utils.getMobileMode();
|
||||
Utils.setMobileMode(!currentMode);
|
||||
},
|
||||
'click .js-card-mobile-desktop-toggle'(event) {
|
||||
event.preventDefault();
|
||||
const currentMode = Utils.getMobileMode();
|
||||
Utils.setMobileMode(!currentMode);
|
||||
},
|
||||
async 'submit .js-card-description'(event) {
|
||||
event.preventDefault();
|
||||
const description = this.currentComponent().getValue();
|
||||
|
|
@ -525,7 +446,7 @@ BlazeComponent.extendComponent({
|
|||
'click .js-add-members': Popup.open('cardMembers'),
|
||||
'click .js-assignee': Popup.open('cardAssignee'),
|
||||
'click .js-add-assignees': Popup.open('cardAssignees'),
|
||||
'click .js-add-labels': Popup.open('cardLabels'),
|
||||
'click .js-add-labels'(event) {Popup.open('cardLabels')(event, { dataContextIfCurrentDataIsUndefined: this.currentData() })},
|
||||
'click .js-received-date': Popup.open('editCardReceivedDate'),
|
||||
'click .js-start-date': Popup.open('editCardStartDate'),
|
||||
'click .js-due-date': Popup.open('editCardDueDate'),
|
||||
|
|
@ -534,12 +455,10 @@ BlazeComponent.extendComponent({
|
|||
'click .js-show-negative-votes': Popup.open('negativeVoteMembers'),
|
||||
'click .js-custom-fields': Popup.open('cardCustomFields'),
|
||||
'mouseenter .js-card-details'() {
|
||||
if (this.parentComponent() === null) return;
|
||||
const parentComponent = this.parentComponent().parentComponent();
|
||||
//on mobile view parent is Board, not BoardBody.
|
||||
if (parentComponent === null) return;
|
||||
parentComponent.showOverlay.set(true);
|
||||
parentComponent.mouseHasEnterCardDetails = true;
|
||||
if (this.boardBody) {
|
||||
this.boardBody.showOverlay.set(true);
|
||||
this.boardBody.mouseHasEnterCardDetails = true;
|
||||
}
|
||||
},
|
||||
'mousedown .js-card-details'() {
|
||||
Session.set('cardDetailsIsDragging', false);
|
||||
|
|
@ -560,13 +479,13 @@ BlazeComponent.extendComponent({
|
|||
'click #toggleCustomFieldsGridButton'() {
|
||||
Meteor.call('toggleCustomFieldsGrid');
|
||||
},
|
||||
'click .js-maximize-card-details'() {
|
||||
'click .js-maximize-card-details'(e) {
|
||||
PopupComponent.maximize(e);
|
||||
Meteor.call('toggleCardMaximized');
|
||||
autosize($('.card-details'));
|
||||
},
|
||||
'click .js-minimize-card-details'() {
|
||||
'click .js-minimize-card-details'(e) {
|
||||
PopupComponent.minimize(e);
|
||||
Meteor.call('toggleCardMaximized');
|
||||
autosize($('.card-details'));
|
||||
},
|
||||
'click .js-vote'(e) {
|
||||
const forIt = $(e.target).hasClass('js-vote-positive');
|
||||
|
|
@ -737,16 +656,6 @@ Template.cardDetails.helpers({
|
|||
return uploadProgressManager.getUploadCountForCard(this._id);
|
||||
}
|
||||
});
|
||||
Template.cardDetailsPopup.onDestroyed(() => {
|
||||
Session.delete('popupCardId');
|
||||
Session.delete('popupCardBoardId');
|
||||
});
|
||||
Template.cardDetailsPopup.helpers({
|
||||
popupCard() {
|
||||
const ret = Utils.getPopupCard();
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
template() {
|
||||
|
|
@ -883,9 +792,7 @@ Template.cardDetailsActionsPopup.events({
|
|||
'click .js-toggle-watch-card'() {
|
||||
const currentCard = this;
|
||||
const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching';
|
||||
Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => {
|
||||
if (!err && ret) Popup.close();
|
||||
});
|
||||
Meteor.call('watch', 'card', currentCard._id, level)
|
||||
},
|
||||
'click .js-toggle-show-list-on-minicard'() {
|
||||
const currentCard = this;
|
||||
|
|
@ -896,9 +803,6 @@ Template.cardDetailsActionsPopup.events({
|
|||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onRendered() {
|
||||
autosize(this.$('textarea.js-edit-card-title'));
|
||||
},
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -979,10 +883,6 @@ const filterMembers = (filterTerm) => {
|
|||
return members;
|
||||
}
|
||||
|
||||
Template.editCardRequesterForm.onRendered(function () {
|
||||
autosize(this.$('.js-edit-card-requester'));
|
||||
});
|
||||
|
||||
Template.editCardRequesterForm.events({
|
||||
'keydown .js-edit-card-requester'(event) {
|
||||
// If enter key was pressed, submit the data
|
||||
|
|
@ -992,10 +892,6 @@ Template.editCardRequesterForm.events({
|
|||
},
|
||||
});
|
||||
|
||||
Template.editCardAssignerForm.onRendered(function () {
|
||||
autosize(this.$('.js-edit-card-assigner'));
|
||||
});
|
||||
|
||||
Template.editCardAssignerForm.events({
|
||||
'keydown .js-edit-card-assigner'(event) {
|
||||
// If enter key was pressed, submit the data
|
||||
|
|
@ -1469,13 +1365,13 @@ BlazeComponent.extendComponent({
|
|||
'DD/MM/YYYY HH:mm',
|
||||
'DD-MM-YYYY HH:mm'
|
||||
];
|
||||
|
||||
|
||||
let parsedDate = null;
|
||||
for (const format of formats) {
|
||||
parsedDate = parseDate(dateString, [format], true);
|
||||
if (parsedDate) break;
|
||||
}
|
||||
|
||||
|
||||
// Fallback to native Date parsing
|
||||
if (!parsedDate) {
|
||||
parsedDate = new Date(dateString);
|
||||
|
|
@ -1721,13 +1617,13 @@ BlazeComponent.extendComponent({
|
|||
'DD/MM/YYYY HH:mm',
|
||||
'DD-MM-YYYY HH:mm'
|
||||
];
|
||||
|
||||
|
||||
let parsedDate = null;
|
||||
for (const format of formats) {
|
||||
parsedDate = parseDate(dateString, [format], true);
|
||||
if (parsedDate) break;
|
||||
}
|
||||
|
||||
|
||||
// Fallback to native Date parsing
|
||||
if (!parsedDate) {
|
||||
parsedDate = new Date(dateString);
|
||||
|
|
@ -1905,9 +1801,6 @@ EscapeActions.register(
|
|||
() => {
|
||||
return !Session.equals('currentCard', null);
|
||||
},
|
||||
{
|
||||
noClickEscapeOn: '.js-card-details,.board-sidebar,#header',
|
||||
},
|
||||
);
|
||||
|
||||
Template.cardAssigneesPopup.onCreated(function () {
|
||||
|
|
@ -1985,3 +1878,16 @@ Template.cardAssigneePopup.events({
|
|||
},
|
||||
'click .js-edit-profile': Popup.open('editProfile'),
|
||||
});
|
||||
|
||||
Template.cardDetailsPopup.helpers({
|
||||
popupArgs() {
|
||||
return {
|
||||
name: "cardDetails",
|
||||
showHeader: false,
|
||||
closeDOMs: ["click .js-close-card-details"],
|
||||
followDOM: ".card-details",
|
||||
handleDOM: ".card-header-middle",
|
||||
closeVar: "currentCard"
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
.card-time {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
padding: 1px 3px;
|
||||
color: #fff;
|
||||
background-color: #dbdbdb;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
textarea.js-add-checklist-item,
|
||||
textarea.js-edit-checklist-item {
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
resize: none;
|
||||
height: 34px;
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ textarea.js-edit-checklist-item {
|
|||
.js-convert-checklist-item-to-card {
|
||||
color: #8c8c8c;
|
||||
text-decoration: underline;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
float: right;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ textarea.js-edit-checklist-item {
|
|||
.checklists-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.checklist-progress-bar-container {
|
||||
display: flex;
|
||||
|
|
@ -35,7 +36,7 @@ textarea.js-edit-checklist-item {
|
|||
margin-right: 10px;
|
||||
}
|
||||
.checklist-progress-bar-container .checklist-progress-bar {
|
||||
width: 80%;
|
||||
flex: 1;
|
||||
height: 10px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 16px;
|
||||
|
|
@ -47,19 +48,29 @@ textarea.js-edit-checklist-item {
|
|||
border-radius: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
.checklist-title {
|
||||
padding: 10px;
|
||||
|
||||
.checklist-controls {
|
||||
display: flex;
|
||||
gap: 0.25lh;
|
||||
}
|
||||
|
||||
.checklist-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.checklist-title .checkbox {
|
||||
float: left;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 18px;
|
||||
|
||||
line-height: 30px;
|
||||
}
|
||||
.checklist-title .title {
|
||||
font-size: 18px;
|
||||
line-height: 25px;
|
||||
.checklist-title p, .title {
|
||||
font-size: 1em;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
}
|
||||
.checklist-title .checklist-stat {
|
||||
margin: 0 0.5em;
|
||||
|
|
@ -79,29 +90,31 @@ textarea.js-edit-checklist-item {
|
|||
bottom: -600px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.checklist {
|
||||
background: #f7f7f7;
|
||||
padding: 0.5lh;
|
||||
margin: 0.5lh 0;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
|
||||
.checklist.placeholder {
|
||||
background: #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.checklist.ui-sortable-helper {
|
||||
box-shadow: -2px 2px 8px rgba(0,0,0,0.3), 0 0 1px rgba(0,0,0,0.5);
|
||||
transform: rotate(4deg);
|
||||
cursor: grabbing;
|
||||
}
|
||||
.checklist-item {
|
||||
margin: 0 0 0 0.1em;
|
||||
line-height: 18px;
|
||||
font-size: 1.1em;
|
||||
margin-top: 3px;
|
||||
display: flex;
|
||||
background: #f7f7f7;
|
||||
gap: 0.25lh;
|
||||
opacity: 1;
|
||||
transition: height 0ms 400ms, opacity 400ms 0ms;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
min-height: 1.5lh;
|
||||
padding: 0 1ch;
|
||||
}
|
||||
.checklist-item.is-checked.invisible {
|
||||
opacity: 0;
|
||||
|
|
@ -114,26 +127,21 @@ textarea.js-edit-checklist-item {
|
|||
background: #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.checklist-item.ui-sortable-helper {
|
||||
box-shadow: -2px 2px 8px rgba(0,0,0,0.3), 0 0 1px rgba(0,0,0,0.5);
|
||||
transform: rotate(4deg);
|
||||
cursor: grabbing;
|
||||
}
|
||||
.checklist-item:hover {
|
||||
background-color: #ebebeb;
|
||||
}
|
||||
.checklist-item .check-box-container {
|
||||
padding-right: 10px;
|
||||
}
|
||||
.checklist-item .check-box {
|
||||
margin: 0.1em 0 0 0;
|
||||
}
|
||||
.checklist-item .check-box.is-checked {
|
||||
border-bottom: 2px solid #3cb500;
|
||||
border-right: 2px solid #3cb500;
|
||||
border-bottom: 0.2ch solid #3cb500;
|
||||
border-right: 0.2ch solid #3cb500;
|
||||
}
|
||||
.checklist-item .item-title {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
flex: 1;
|
||||
cursor: grab;
|
||||
}
|
||||
.checklist-item .item-title.is-checked {
|
||||
color: #8c8c8c;
|
||||
|
|
@ -141,27 +149,18 @@ textarea.js-edit-checklist-item {
|
|||
text-decoration: line-through;
|
||||
}
|
||||
.checklist-item .item-title .viewer p {
|
||||
margin-bottom: 2px;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
display: flex;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 420px;
|
||||
}
|
||||
.checklist-item span.fa.checklistitem-handle {
|
||||
padding-top: 2px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.js-delete-checklist-item,
|
||||
.js-convert-checklist-item-to-card {
|
||||
margin: 0 0 0.5em 1.33em;
|
||||
padding: 12px 0 0 0;
|
||||
}
|
||||
.add-checklist-item {
|
||||
margin: 0.2em 0 0.5em 1.33em;
|
||||
}
|
||||
.add-checklist-item.js-open-inlined-form,
|
||||
.add-checklist.js-open-inlined-form {
|
||||
display: block;
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
.add-checklist-item.js-open-inlined-form:hover,
|
||||
.add-checklist.js-open-inlined-form:hover {
|
||||
|
|
@ -169,25 +168,13 @@ textarea.js-edit-checklist-item {
|
|||
color: #222;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
}
|
||||
.add-checklist-top {
|
||||
/* more space to checklists title */
|
||||
padding-left: 20px;
|
||||
/* + is easier clickable */
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.add-checklist-top.js-open-inlined-form:hover {
|
||||
background: #dbdbdb;
|
||||
color: #222;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.2);
|
||||
}
|
||||
.card-details-item-title {
|
||||
/* max width for adding checklist at top */
|
||||
width: 100%;
|
||||
}
|
||||
.checklist-details-menu {
|
||||
float: right;
|
||||
padding: 6px 10px 6px 10px;
|
||||
}
|
||||
|
||||
.edit-controls label.toggle-label {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,26 +36,31 @@ template(name="checklistDetail")
|
|||
+editChecklistItemForm(checklist = checklist)
|
||||
else
|
||||
.checklist-title
|
||||
span
|
||||
if canModifyCard
|
||||
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
|
||||
|
||||
if canModifyCard
|
||||
h4.title.js-open-inlined-form.is-editable
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
|
||||
h4.title
|
||||
if canModifyCard
|
||||
a.js-open-inlined-form.is-editable(title="{{_ 'moveChecklistPopup-title'}}")
|
||||
+viewer
|
||||
= checklist.title
|
||||
else
|
||||
+viewer
|
||||
= checklist.title
|
||||
else
|
||||
h4.title
|
||||
+viewer
|
||||
.checklist-controls
|
||||
if canModifyCard
|
||||
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
|
||||
if isMiniScreen
|
||||
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
|
||||
+viewer
|
||||
= checklist.title
|
||||
|
||||
if $gt finishedPercent 0
|
||||
.checklist-progress-bar-container
|
||||
.checklist-progress-text {{finishedPercent}}%
|
||||
.checklist-progress-bar
|
||||
//- jumps where checking the first item is not comfortable;
|
||||
//- so try to show it anytime. also, it helps to separate the checklists.
|
||||
.checklist-progress-bar-container
|
||||
.checklist-progress-text {{finishedPercent}}%
|
||||
.checklist-progress-bar
|
||||
if $gt finishedPercent 0
|
||||
.checklist-progress(style="width:{{finishedPercent}}%")
|
||||
else
|
||||
.checklist-progress(style="visibility:hidden")
|
||||
+checklistItems(checklist = checklist card = card)
|
||||
|
||||
template(name="checklistDeletePopup")
|
||||
|
|
@ -64,7 +69,7 @@ template(name="checklistDeletePopup")
|
|||
|
||||
template(name="addChecklistItemForm")
|
||||
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
|
||||
span.copied-tooltip {{_ 'copied'}}
|
||||
span.copied-tooltip.copied-tooltip-hidden {{_ 'copied'}}
|
||||
textarea.js-add-checklist-item(rows='1' autofocus)
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}}
|
||||
|
|
@ -73,16 +78,12 @@ template(name="addChecklistItemForm")
|
|||
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
|
||||
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
|
||||
label.toggle-label(for="toggleNewlineBecomesNewChecklistItem")
|
||||
span.toggle-switch-desc
|
||||
| {{_ 'newLineNewItem'}}
|
||||
if $eq position 'top'
|
||||
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItemOriginOrder'}}")
|
||||
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItemOriginOrder")
|
||||
label.toggle-label(for="toggleNewlineBecomesNewChecklistItemOriginOrder")
|
||||
| {{_ 'originOrder'}}
|
||||
|
||||
template(name="editChecklistItemForm")
|
||||
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
|
||||
span.copied-tooltip {{_ 'copied'}}
|
||||
span.copied-tooltip.copied-tooltip-hidden {{_ 'copied'}}
|
||||
textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
|
||||
if $eq type 'item'
|
||||
= item.title
|
||||
|
|
@ -99,13 +100,6 @@ template(name="editChecklistItemForm")
|
|||
| {{_ 'convertChecklistItemToCardPopup-title'}}
|
||||
|
||||
template(name="checklistItems")
|
||||
if checklist.items.length
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist position="top")
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
|
||||
else
|
||||
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
|
||||
i.fa.fa-plus
|
||||
.checklist-items.js-checklist-items
|
||||
each item in checklist.items
|
||||
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
|
||||
|
|
@ -118,14 +112,15 @@ template(name="checklistItems")
|
|||
else
|
||||
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
|
||||
i.fa.fa-plus
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
|
||||
|
||||
template(name='checklistItemDetail')
|
||||
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
|
||||
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
|
||||
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}" role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
|
||||
if canModifyCard
|
||||
.check-box-container
|
||||
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
if isMiniScreen
|
||||
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
|
||||
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||
+viewer
|
||||
|
|
@ -141,16 +136,16 @@ template(name="checklistActionsPopup")
|
|||
li
|
||||
a.js-delete-checklist.delete-checklist
|
||||
i.fa.fa-trash
|
||||
| {{_ "delete"}} ...
|
||||
| {{_ "delete"}}
|
||||
a.js-move-checklist.move-checklist
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ "moveChecklist"}} ...
|
||||
| {{_ "moveChecklist"}}
|
||||
a.js-copy-checklist.copy-checklist
|
||||
i.fa.fa-copy
|
||||
| {{_ "copyChecklist"}} ...
|
||||
| {{_ "copyChecklist"}}
|
||||
a.js-hide-checked-checklist-items
|
||||
i.fa.fa-eye-slash
|
||||
| {{_ "hideCheckedChecklistItems"}} ...
|
||||
| {{_ "hideCheckedChecklistItems"}}
|
||||
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
|
||||
if checklist.hideCheckedChecklistItems
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}" checked="checked")
|
||||
|
|
@ -158,7 +153,7 @@ template(name="checklistActionsPopup")
|
|||
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
|
||||
label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}")
|
||||
a.js-hide-all-checklist-items
|
||||
i.fa.fa-ban
|
||||
| 🚫
|
||||
| {{_ "hideAllChecklistItems"}} ...
|
||||
.material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}")
|
||||
if checklist.hideAllChecklistItems
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwim
|
|||
const subManager = new SubsManager();
|
||||
const { calculateIndexData, capitalize } = Utils;
|
||||
|
||||
function initSorting(items) {
|
||||
function initSorting(items, handleSelector) {
|
||||
items.sortable({
|
||||
tolerance: 'pointer',
|
||||
helper: 'clone',
|
||||
|
|
@ -16,6 +16,7 @@ function initSorting(items) {
|
|||
appendTo: 'parent',
|
||||
distance: 7,
|
||||
placeholder: 'checklist-item placeholder',
|
||||
handle: handleSelector,
|
||||
scroll: true,
|
||||
start(evt, ui) {
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
|
|
@ -48,8 +49,9 @@ function initSorting(items) {
|
|||
BlazeComponent.extendComponent({
|
||||
onRendered() {
|
||||
const self = this;
|
||||
this.handleSelector = Utils.isMiniScreen() ? 'span.fa.checklistitem-handle' : '.item-title';
|
||||
self.itemsDom = this.$('.js-checklist-items');
|
||||
initSorting(self.itemsDom);
|
||||
initSorting(self.itemsDom, this.handleSelector);
|
||||
self.itemsDom.mousedown(function (evt) {
|
||||
evt.stopPropagation();
|
||||
});
|
||||
|
|
@ -63,11 +65,9 @@ BlazeComponent.extendComponent({
|
|||
const $itemsDom = $(self.itemsDom);
|
||||
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
|
||||
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
|
||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
$(self.itemsDom).sortable({
|
||||
handle: 'span.fa.checklistitem-handle',
|
||||
});
|
||||
}
|
||||
$(self.itemsDom).sortable({
|
||||
handle: this.handleSelector,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
.card-label {
|
||||
border: 1px solid #000;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 5px;
|
||||
padding: 3px 8px;
|
||||
max-width: 210px;
|
||||
min-width: 8px;
|
||||
word-wrap: break-word;
|
||||
min-height: 18px;
|
||||
vertical-align: middle;
|
||||
white-space: initial;
|
||||
overflow: initial;
|
||||
font-size: 0.9em;
|
||||
display: flex;
|
||||
/* prefer not using padding/margin but let outer grids
|
||||
position/size labels (see e.g. minicards), otherwise we get
|
||||
inconsistencies */
|
||||
align-self: stretch;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 0 0.5ch;
|
||||
height: var(--label-height);
|
||||
min-width: 8ch;
|
||||
}
|
||||
.card-label:hover {
|
||||
color: #fff;
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
}
|
||||
.card-label p {
|
||||
margin: 0px;
|
||||
--overflow-lines: 1;
|
||||
}
|
||||
.palette-colors {
|
||||
display: flex;
|
||||
|
|
@ -138,37 +139,22 @@
|
|||
.card-label-indigo {
|
||||
background-color: #4b0082;
|
||||
}
|
||||
.edit-label .card-label,
|
||||
.create-label .card-label {
|
||||
float: left;
|
||||
height: 25px;
|
||||
margin: 0px 3% 7px 0px;
|
||||
width: 10.5%;
|
||||
max-width: 10.5%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.edit-labels input[type="text"] {
|
||||
margin: 4px 0 6px 38px;
|
||||
width: 243px;
|
||||
}
|
||||
.edit-labels .card-label {
|
||||
height: 30px;
|
||||
left: 0;
|
||||
padding: 1px 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.edit-labels .labels-static .card-label {
|
||||
line-height: 30px;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: 0;
|
||||
width: 260px;
|
||||
}
|
||||
.edit-labels-pop-over {
|
||||
margin-bottom: 8px;
|
||||
display: grid;
|
||||
/* so that inner elements, align nicely */
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.1lh;
|
||||
>li {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 1ch;
|
||||
align-items: center;
|
||||
}
|
||||
.card-label-selectable {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 1ch;
|
||||
}
|
||||
}
|
||||
.edit-labels-pop-over .card-label .viewer p {
|
||||
margin: 0;
|
||||
|
|
@ -176,34 +162,6 @@
|
|||
.edit-labels-pop-over .shortcut {
|
||||
display: inline-block;
|
||||
}
|
||||
.card-label-selectable {
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
margin-bottom: 3px;
|
||||
width: 190px;
|
||||
min-height: 18px;
|
||||
padding: 8px;
|
||||
position: relative;
|
||||
transition: margin-right 0.1s;
|
||||
}
|
||||
.card-label-selectable .card-label-selectable-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: -20px;
|
||||
}
|
||||
.card-label-selectable.active:hover,
|
||||
.card-label-selectable.active,
|
||||
.card-label-selectable.active.selected:hover,
|
||||
.card-label-selectable.active.selected {
|
||||
padding-right: 32px;
|
||||
}
|
||||
.card-label-selectable.active:hover .card-label-selectable-icon,
|
||||
.card-label-selectable.active .card-label-selectable-icon,
|
||||
.card-label-selectable.active.selected:hover .card-label-selectable-icon,
|
||||
.card-label-selectable.active.selected .card-label-selectable-icon {
|
||||
right: 6px;
|
||||
}
|
||||
.card-label-selectable.selected,
|
||||
.card-label-selectable:hover {
|
||||
opacity: 0.8;
|
||||
|
|
@ -212,24 +170,6 @@
|
|||
.active .card-label-selectable:hover {
|
||||
margin-right: 0;
|
||||
}
|
||||
.active .card-label-selectable .card-label-selectable-icon {
|
||||
right: 8px;
|
||||
}
|
||||
.card-label-edit-button {
|
||||
border-radius: 3px;
|
||||
float: right;
|
||||
padding: 8px;
|
||||
}
|
||||
.card-label-edit-button:hover {
|
||||
background: #dbdbdb;
|
||||
}
|
||||
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.label-handle + .card-label {
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,32 @@
|
|||
.minicard-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 1ch 0.2lh 1ch;
|
||||
gap: 0.2lh;
|
||||
}
|
||||
|
||||
.minicard-wrapper {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1.2vh;
|
||||
height: min-content;
|
||||
}
|
||||
.minicard-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1ch;
|
||||
gap: 1ch;
|
||||
}
|
||||
|
||||
.minicard > hr {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.minicard-add-form {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.minicard-wrapper.placeholder {
|
||||
background: #ccc;
|
||||
border-radius: 1.2vw;
|
||||
|
|
@ -28,32 +50,25 @@
|
|||
.minicard-wrapper .multi-selection-checkbox + .minicard {
|
||||
margin-left: 1vw;
|
||||
}
|
||||
@media only screen {
|
||||
.minicard {
|
||||
padding: 0.8vh 1vw 0.3vh;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
background-color: #fff;
|
||||
min-height: 2.5vh;
|
||||
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
|
||||
border-radius: 0.3vw;
|
||||
color: #4d4d4d;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s, border-radius 0.2s;
|
||||
}
|
||||
.minicard {
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
grid-template-rows: min-content 1fr auto auto;
|
||||
gap: 0.4lh;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
|
||||
border-radius: 0.3vw;
|
||||
color: #4d4d4d;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s, border-radius 0.2s;
|
||||
flex: 1;
|
||||
}
|
||||
.minicard-details-menu-with-handle {
|
||||
float: right;
|
||||
padding-left: 0.7vw;
|
||||
font-size: clamp(14px, 3vw, 18px);
|
||||
padding: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.minicard-details-menu {
|
||||
float: right;
|
||||
font-size: clamp(14px, 3vw, 18px);
|
||||
padding-left: 0.7vw;
|
||||
|
||||
.minicard-actions-right {
|
||||
justify-content: end;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: .5lh;
|
||||
}
|
||||
@media print {
|
||||
.minicard-details-menu,
|
||||
|
|
@ -76,7 +91,6 @@
|
|||
transform: translateX(1.5vw);
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
z-index: 25;
|
||||
box-shadow: -0.3vw 0.2vh 0.3vh rgba(0,0,0,0.2);
|
||||
}
|
||||
.minicard:hover:not(.minicard-composer),
|
||||
|
|
@ -96,20 +110,30 @@
|
|||
margin: 0.8vh -1vw 0.8vh -1vw;
|
||||
border-radius: top 0.3vw;
|
||||
}
|
||||
.minicard .minicard-labels {
|
||||
float: none;
|
||||
margin-right: 6vw;
|
||||
}
|
||||
.minicard .minicard-labels .minicard-label {
|
||||
width: clamp(12px, 1.5vw, 16px);
|
||||
height: clamp(12px, 1.5vw, 16px);
|
||||
border-radius: 0.3vw;
|
||||
margin-right: 0.4vw;
|
||||
margin-bottom: 0.4vh;
|
||||
}
|
||||
.minicard .minicard-labels-no-text {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.minicard {
|
||||
.minicard-labels, .dates {
|
||||
display: grid;
|
||||
grid-auto-rows: min-content;
|
||||
justify-content: stretch;
|
||||
font-size: 0.8em;
|
||||
grid-auto-rows: minmax(1.3lh, auto);
|
||||
}
|
||||
.minicard-labels {
|
||||
grid-template-columns: repeat(auto-fill, minmax(12ch, auto));
|
||||
gap: 0.2lh 0.5ch;
|
||||
}
|
||||
.minicard-labels-no-text {
|
||||
grid-template-columns: repeat(auto-fill, 4ch);
|
||||
grid-template-rows: 4ch;
|
||||
font-size: 0.4em;
|
||||
.minicard-label {
|
||||
border-radius: 1ch;
|
||||
}
|
||||
}
|
||||
.dates {
|
||||
height: min-content;
|
||||
grid-template-columns: repeat(auto-fit, minmax(15ch, auto));
|
||||
}
|
||||
}
|
||||
.minicard .minicard-custom-fields {
|
||||
display: block;
|
||||
|
|
@ -121,26 +145,22 @@
|
|||
.minicard .minicard-custom-field-item {
|
||||
flex-grow: 1;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 13vw;
|
||||
margin-right: 0.5vw;
|
||||
}
|
||||
.minicard .minicard-custom-field-item-fullwidth {
|
||||
flex-grow: 1;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
margin-right: 0.5vw;
|
||||
}
|
||||
.minicard .handle {
|
||||
width: clamp(20px, 2.5vw, 28px);
|
||||
height: clamp(20px, 2.5vw, 28px);
|
||||
position: absolute;
|
||||
right: 0vw;
|
||||
top: 4vh;
|
||||
display: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media only screen {
|
||||
.minicard .handle {
|
||||
display: block;
|
||||
|
|
@ -154,58 +174,34 @@
|
|||
text-align: center;
|
||||
}
|
||||
.minicard .minicard-title {
|
||||
margin-right: 1.5vw;
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
cursor: grab;
|
||||
.viewer {
|
||||
--overflow-lines: 2;
|
||||
}
|
||||
|
||||
}
|
||||
.minicard .minicard-title .card-number {
|
||||
color: #b3b3b3;
|
||||
display: inline-block;
|
||||
margin-right: 0.7vw;
|
||||
}
|
||||
@media only screen {
|
||||
.minicard .minicard-title p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.minicard .minicard-title .viewer {
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
.minicard .dates {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
margin-right: 6vw;
|
||||
clear: both;
|
||||
}
|
||||
.minicard .date {
|
||||
margin-right: 0.4vw;
|
||||
display: flex;
|
||||
&>a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unicode icons for minicard dates - matching cardDate.css */
|
||||
.minicard .card-date.end-date time::before {
|
||||
content: "🏁"; /* Finish flag - represents end/completion */
|
||||
}
|
||||
.minicard .card-date.due-date time::before {
|
||||
content: "⏰"; /* Alarm clock - represents due/deadline */
|
||||
}
|
||||
.minicard .card-date.start-date time::before {
|
||||
content: "🚀"; /* Rocket - represents start/launch */
|
||||
}
|
||||
.minicard .card-date.received-date time::before {
|
||||
content: "📥"; /* Inbox tray - represents received/incoming */
|
||||
}
|
||||
|
||||
.minicard .card-date time::before {
|
||||
font-size: inherit;
|
||||
margin-right: 0.3em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Date type specific colors for minicards - matching cardDate.css */
|
||||
.minicard .card-date.received-date {
|
||||
background-color: #dbdbdb; /* Grey for received - same as base card-date */
|
||||
background-color: #d3d3d3; /* Grey for received - a bit darker than base card-date */
|
||||
}
|
||||
|
||||
.minicard .card-date.received-date:hover,
|
||||
|
|
@ -311,102 +307,134 @@
|
|||
background-color: #1976d2 !important;
|
||||
}
|
||||
|
||||
.minicard .minicard-badges-and-creator {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: end;
|
||||
gap: 0 0.5ch;;
|
||||
}
|
||||
|
||||
.minicard-people-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-auto-rows: auto;
|
||||
}
|
||||
|
||||
.minicard-people-wrapper {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
gap: 0.1lh;
|
||||
}
|
||||
.minicard .badges {
|
||||
float: left;
|
||||
margin-top: 1vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5ch;
|
||||
color: #808080;
|
||||
/* this avoid padding-ish at the bottom of the card */
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.minicard .badges:empty {
|
||||
display: none;
|
||||
}
|
||||
.minicard .badges .badge {
|
||||
float: left;
|
||||
margin-right: 1.5vw;
|
||||
margin-bottom: 0.4vh;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.minicard .badges .badge.is-finished {
|
||||
background: #3cb500;
|
||||
padding: 0 0.4vw;
|
||||
padding: 0.3lh 0.8ch;
|
||||
border-radius: 0.4vw;
|
||||
color: #fff;
|
||||
&, .fa {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.minicard .badges .badge:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
.minicard .badges .badge .badge-icon,
|
||||
.minicard .badges .badge .badge-text {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.minicard .badges .badge .badge-icon.badge-comment,
|
||||
.minicard .badges .badge .badge-text.badge-comment {
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
.minicard .badges .badge .badge-text {
|
||||
font-size: 0.9em;
|
||||
padding-left: 0.3vw;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.minicard .badges .badge .check-list-text {
|
||||
padding-left: 0px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.minicard .minicard-members,
|
||||
.minicard .minicard-assignees,
|
||||
.minicard .minicard-creator {
|
||||
float: right;
|
||||
margin-left: 0.7vw;
|
||||
margin-bottom: 0.5vh;
|
||||
}
|
||||
.minicard .minicard-members .member,
|
||||
.minicard .minicard-assignees .member,
|
||||
.minicard .minicard-creator .member {
|
||||
float: right;
|
||||
display: flex;
|
||||
border-radius: 50%;
|
||||
height: clamp(24px, 3.5vw, 32px);
|
||||
width: clamp(24px, 3.5vw, 32px);
|
||||
margin-bottom: 0.5vh;
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 0.2lh;
|
||||
}
|
||||
.minicard .minicard-members .assignee,
|
||||
.minicard .minicard-assignees .assignee,
|
||||
.minicard .minicard-creator .assignee {
|
||||
float: right;
|
||||
border-radius: 50%;
|
||||
height: clamp(24px, 3.5vw, 32px);
|
||||
width: clamp(24px, 3.5vw, 32px);
|
||||
|
||||
.minicard .minicard-assignees .member {
|
||||
border: 2px solid rgb(180, 87, 87);
|
||||
}
|
||||
.minicard .minicard-members + .badges,
|
||||
.minicard .minicard-assignees + .badges,
|
||||
.minicard .minicard-creator + .badges {
|
||||
margin-top: 0.7vh;
|
||||
|
||||
.minicard .minicard-creator .member {
|
||||
border: 2px solid #7fd67f;
|
||||
}
|
||||
|
||||
.minicard .minicard-members .member {
|
||||
border: 2px solid #5a5ac6;
|
||||
}
|
||||
.minicard .minicard-assignees {
|
||||
border-bottom: 1px solid #f00;
|
||||
}
|
||||
.minicard .minicard-creator {
|
||||
border-bottom: 1px solid #008000;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.minicard .minicard-members:empty,
|
||||
.minicard .minicard-assignees:empty {
|
||||
display: none;
|
||||
}
|
||||
.minicard .minicard-description {
|
||||
padding: 0.8vh 0 0 1vw;
|
||||
color: #000;
|
||||
background-color: #eee;
|
||||
width: 100%;
|
||||
margin-bottom: 0.3vh;
|
||||
margin-left: -0.5vw;
|
||||
border-radius: 0.4vw;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
padding: 0.5lh 1ch;
|
||||
--overflow-lines: 2;
|
||||
.viewer {
|
||||
font-size: 0.9em;
|
||||
ul {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.minicard .minicard-description p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.minicard-composer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.minicard-composer-icons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.2lh;
|
||||
}
|
||||
|
||||
.minicard-bottom {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
gap: 1ch;
|
||||
|
||||
.minicard-composer-icons {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.add-controls {
|
||||
display: flex;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.minicard.minicard-composer {
|
||||
margin-bottom: 1.3vh;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
gap: 0.3lh;
|
||||
padding: 0.5lh 1ch;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.minicard.minicard-composer textarea.minicard-composer-textarea,
|
||||
.minicard.minicard-composer textarea.minicard-composer-textarea:focus {
|
||||
resize: none;
|
||||
|
|
@ -415,11 +443,11 @@
|
|||
box-shadow: none;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 22vh;
|
||||
min-height: 5vh;
|
||||
margin-bottom: 2.5vh;
|
||||
padding: 1ch;
|
||||
min-height: 5lh;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
.parent-prefix {
|
||||
color: #b3b3b3;
|
||||
|
|
@ -734,30 +762,12 @@
|
|||
|
||||
/* List name display on minicard */
|
||||
.minicard-list-name {
|
||||
font-size: 0.75em;
|
||||
font-size: inherit;
|
||||
color: #8c8c8c;
|
||||
margin-top: 0.2vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3vw;
|
||||
}
|
||||
|
||||
/* Checklist display on minicard */
|
||||
.minicard-checklist {
|
||||
width: 100%;
|
||||
margin-top: 0.5vh;
|
||||
margin-bottom: 0.5vh;
|
||||
padding: 0.3vh 0.5vw;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 0.3vw;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.minicard-checklist .checklist-header {
|
||||
padding: 0 0.5ch;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.3vh;
|
||||
gap: 0.5ch;
|
||||
}
|
||||
|
||||
.minicard-checklist .checklist-title {
|
||||
|
|
|
|||
|
|
@ -1,226 +1,236 @@
|
|||
template(name="minicard")
|
||||
if isSelected
|
||||
+cardDetailsPopup(this)
|
||||
.minicard.nodragscroll(
|
||||
class="{{#if isLinkedCard}}linked-card{{/if}}"
|
||||
class="{{#if isLinkedBoard}}linked-board{{/if}}"
|
||||
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
|
||||
if canMoveCard
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
.handle
|
||||
i.fa.fa-arrows
|
||||
if canModifyCard
|
||||
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
.dates
|
||||
if getReceived
|
||||
.date
|
||||
+minicardReceivedDate
|
||||
if getStart
|
||||
.date
|
||||
+minicardStartDate
|
||||
if getDue
|
||||
.date
|
||||
+minicardDueDate
|
||||
if getEnd
|
||||
+minicardEndDate
|
||||
if allowsReceivedDate
|
||||
if getReceived
|
||||
.date.viewer
|
||||
+minicardReceivedDate
|
||||
if allowsStartDate
|
||||
if getStart
|
||||
.date.viewer
|
||||
+minicardStartDate
|
||||
if allowsDueDate
|
||||
if getDue
|
||||
.date.viewer
|
||||
+minicardDueDate
|
||||
if allowsEndDate
|
||||
if getEnd
|
||||
.date.viewer
|
||||
+minicardEndDate
|
||||
if getSpentTime
|
||||
.date
|
||||
.date.viewer
|
||||
+cardSpentTime
|
||||
if cover
|
||||
if currentBoard.allowsCoverAttachmentOnMinicard
|
||||
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
|
||||
.minicard-header
|
||||
.minicard-title
|
||||
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
||||
.parent-prefix
|
||||
| {{ parentString ' > ' }}
|
||||
if $eq 'prefix-with-parent' currentBoard.presentParentTask
|
||||
.parent-prefix
|
||||
| {{ parentCardName }}
|
||||
if isLinkedBoard
|
||||
a.js-linked-link
|
||||
span.linked-icon
|
||||
i.fa.fa-folder
|
||||
else if isLinkedCard
|
||||
a.js-linked-link
|
||||
span.linked-icon
|
||||
i.fa.fa-id-card
|
||||
if getArchived
|
||||
span.linked-icon.linked-archived
|
||||
i.fa.fa-archive
|
||||
+viewer
|
||||
if allowsCardNumber
|
||||
span.card-number
|
||||
| ##{getCardNumber} 
|
||||
= getTitle
|
||||
div.minicard-actions-right
|
||||
if canModifyCard
|
||||
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
if isMiniScreen
|
||||
if canMoveCard
|
||||
.handle
|
||||
i.fa.fa-arrows
|
||||
hr
|
||||
.minicard-body
|
||||
if cover
|
||||
if allowsCoverAttachmentOnMinicard
|
||||
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
|
||||
|
||||
// Upload progress indicator for drag-and-drop uploads
|
||||
if hasActiveUploads
|
||||
.minicard-upload-progress
|
||||
.upload-progress-header
|
||||
i.fa.fa-upload
|
||||
span {{_ 'uploading-files'}} ({{uploadCount}})
|
||||
each uploads
|
||||
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
|
||||
.upload-progress-filename {{file.name}}
|
||||
.upload-progress-bar
|
||||
.upload-progress-fill(style="width: {{progress}}%")
|
||||
if $eq status 'error'
|
||||
.upload-progress-error
|
||||
i.fa.fa-warning
|
||||
span {{_ 'upload-failed'}}
|
||||
else if $eq status 'completed'
|
||||
.upload-progress-success
|
||||
i.fa.fa-check
|
||||
span {{_ 'upload-completed'}}
|
||||
//- Upload progress indicator for drag-and-drop uploads
|
||||
if hasActiveUploads
|
||||
.minicard-upload-progress
|
||||
.upload-progress-header
|
||||
i.fa.fa-upload
|
||||
span {{_ 'uploading-files'}} ({{uploadCount}})
|
||||
each uploads
|
||||
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
|
||||
.upload-progress-filename {{file.name}}
|
||||
.upload-progress-bar
|
||||
.upload-progress-fill(style="width: {{progress}}%")
|
||||
if $eq status 'error'
|
||||
.upload-progress-error
|
||||
i.fa.fa-warning
|
||||
span {{_ 'upload-failed'}}
|
||||
else if $eq status 'completed'
|
||||
.upload-progress-success
|
||||
i.fa.fa-check
|
||||
span {{_ 'upload-completed'}}
|
||||
|
||||
.minicard-title
|
||||
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
||||
.parent-prefix
|
||||
| {{ parentString ' > ' }}
|
||||
if $eq 'prefix-with-parent' currentBoard.presentParentTask
|
||||
.parent-prefix
|
||||
| {{ parentCardName }}
|
||||
if isLinkedBoard
|
||||
a.js-linked-link
|
||||
span.linked-icon
|
||||
i.fa.fa-folder
|
||||
else if isLinkedCard
|
||||
a.js-linked-link
|
||||
span.linked-icon
|
||||
i.fa.fa-id-card
|
||||
if getArchived
|
||||
span.linked-icon.linked-archived
|
||||
i.fa.fa-archive
|
||||
+viewer
|
||||
if currentBoard.allowsCardNumber
|
||||
span.card-number
|
||||
| ##{getCardNumber}
|
||||
= getTitle
|
||||
if labels
|
||||
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
|
||||
each labels
|
||||
unless hiddenMinicardLabelText
|
||||
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
|
||||
+viewer
|
||||
= name
|
||||
if hiddenMinicardLabelText
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
if labels
|
||||
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
|
||||
each labels
|
||||
unless hiddenMinicardLabelText
|
||||
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
|
||||
+viewer
|
||||
= name
|
||||
if hiddenMinicardLabelText
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
|
||||
.minicard-custom-fields
|
||||
each customFieldsWD
|
||||
if definition.showOnCard
|
||||
if trueValue
|
||||
.minicard-custom-field
|
||||
// If there is custom field label, show label at left,
|
||||
// and value at right
|
||||
if definition.showLabelOnMiniCard
|
||||
.minicard-custom-field-item
|
||||
+viewer
|
||||
= definition.name
|
||||
.minicard-custom-field-item
|
||||
if $eq definition.type "currency"
|
||||
.minicard-custom-fields
|
||||
each customFieldsWD
|
||||
if definition.showOnCard
|
||||
if trueValue
|
||||
.minicard-custom-field
|
||||
// If there is custom field label, show label at left,
|
||||
// and value at right
|
||||
if definition.showLabelOnMiniCard
|
||||
.minicard-custom-field-item
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(definition)
|
||||
else if $eq definition.type "date"
|
||||
.date
|
||||
+minicardCustomFieldDate
|
||||
else if $eq definition.type "checkbox"
|
||||
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
|
||||
else if $eq definition.type "stringtemplate"
|
||||
+viewer
|
||||
= formattedStringtemplateCustomFieldValue(definition)
|
||||
else
|
||||
+viewer
|
||||
= trueValue
|
||||
else
|
||||
// If there is no custom field label,
|
||||
// show value full width
|
||||
.minicard-custom-field-item-fullwidth
|
||||
if $eq definition.type "currency"
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(definition)
|
||||
else if $eq definition.type "date"
|
||||
.date
|
||||
+minicardCustomFieldDate
|
||||
else if $eq definition.type "checkbox"
|
||||
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
|
||||
else if $eq definition.type "stringtemplate"
|
||||
+viewer
|
||||
= formattedStringtemplateCustomFieldValue(definition)
|
||||
else
|
||||
+viewer
|
||||
= trueValue
|
||||
= definition.name
|
||||
.minicard-custom-field-item
|
||||
if $eq definition.type "currency"
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(definition)
|
||||
else if $eq definition.type "date"
|
||||
.date
|
||||
+minicardCustomFieldDate
|
||||
else if $eq definition.type "checkbox"
|
||||
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
|
||||
else if $eq definition.type "stringtemplate"
|
||||
+viewer
|
||||
= formattedStringtemplateCustomFieldValue(definition)
|
||||
else
|
||||
+viewer
|
||||
= trueValue
|
||||
else
|
||||
// If there is no custom field label,
|
||||
// show value full width
|
||||
.minicard-custom-field-item-fullwidth
|
||||
if $eq definition.type "currency"
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(definition)
|
||||
else if $eq definition.type "date"
|
||||
.date
|
||||
+minicardCustomFieldDate
|
||||
else if $eq definition.type "checkbox"
|
||||
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
|
||||
else if $eq definition.type "stringtemplate"
|
||||
+viewer
|
||||
= formattedStringtemplateCustomFieldValue(definition)
|
||||
else
|
||||
+viewer
|
||||
= trueValue
|
||||
.minicard-people-grid
|
||||
if allowsAssignee
|
||||
if getAssignees
|
||||
.minicard-people-wrapper
|
||||
.minicard-assignees.js-minicard-assignees
|
||||
each getAssignees
|
||||
+userAvatar(userId=this)
|
||||
|
||||
if showAssignee
|
||||
if getAssignees
|
||||
.minicard-assignees.js-minicard-assignees
|
||||
each getAssignees
|
||||
+userAvatar(userId=this)
|
||||
if allowsMembers
|
||||
if getMembers
|
||||
.minicard-people-wrapper
|
||||
.minicard-members.js-minicard-members
|
||||
each getMembers
|
||||
+userAvatar(userId=this)
|
||||
|
||||
if showMembers
|
||||
if getMembers
|
||||
.minicard-members.js-minicard-members
|
||||
each getMembers
|
||||
+userAvatar(userId=this)
|
||||
.minicard-badges-and-creator
|
||||
if allowsCreatorOnMinicard
|
||||
.minicard-creator
|
||||
+userAvatar(userId=this.userId noRemove=true)
|
||||
|
||||
if showCreatorOnMinicard
|
||||
.minicard-creator
|
||||
+userAvatar(userId=this.userId noRemove=true)
|
||||
|
||||
.badges
|
||||
if canModifyCard
|
||||
if comments.length
|
||||
.badge(title="{{_ 'card-comments-title' comments.length }}")
|
||||
span.badge-icon.badge-comment.badge-text
|
||||
i.fa.fa-comment-o
|
||||
= ' '
|
||||
= comments.length
|
||||
//span.badge-comment.badge-text
|
||||
//| {{_ 'comment'}}
|
||||
if getDescription
|
||||
unless currentBoard.allowsDescriptionTextOnMinicard
|
||||
.badge.badge-state-image-only(title=getDescription)
|
||||
span.badge-icon
|
||||
i.fa.fa-file-text-o
|
||||
if getVoteQuestion
|
||||
.badge.badge-state-image-only(title=getVoteQuestion)
|
||||
span.badge-icon(class="{{#if voteState}}text-green{{/if}}")
|
||||
i.fa.fa-thumbs-up
|
||||
span.badge-text {{ voteCountPositive }}
|
||||
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}")
|
||||
i.fa.fa-thumbs-down
|
||||
span.badge-text {{ voteCountNegative }}
|
||||
if getPokerQuestion
|
||||
.badge.badge-state-image-only(title=getPokerQuestion)
|
||||
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}")
|
||||
i.fa.fa-check-square
|
||||
if expiredPoker
|
||||
span.badge-text {{ getPokerEstimation }}
|
||||
if attachments.length
|
||||
if currentBoard.allowsBadgeAttachmentOnMinicard
|
||||
.badge
|
||||
span.badge-icon
|
||||
i.fa.fa-paperclip
|
||||
span.badge-text= attachments.length
|
||||
if allSubtasks.count
|
||||
.badge
|
||||
span.badge-icon
|
||||
i.fa.fa-globe
|
||||
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
|
||||
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
|
||||
if currentBoard.allowsCardSortingByNumber
|
||||
if currentBoard.allowsCardSortingByNumberOnMinicard
|
||||
.badge
|
||||
span.badge-icon
|
||||
i.fa.fa-sort-numeric-asc
|
||||
span.badge-text.check-list-sort {{ sort }}
|
||||
if shouldShowChecklistAtMinicard
|
||||
each shouldShowChecklistAtMinicard
|
||||
+minicardChecklist(checklist=. card=..)
|
||||
if currentBoard.allowsDescriptionTextOnMinicard
|
||||
.badges
|
||||
if canModifyCard
|
||||
if allowsComments
|
||||
if comments.length
|
||||
.badge(title="{{_ 'card-comments-title' comments.length }}")
|
||||
span.badge-icon.badge-comment.badge-text
|
||||
i.fa.fa-comment-o
|
||||
= ' '
|
||||
= comments.length
|
||||
//span.badge-comment.badge-text
|
||||
//| {{_ 'comment'}}
|
||||
if getDescription
|
||||
unless allowsDescriptionTextOnMinicard
|
||||
.badge.badge-state-image-only(title=getDescription)
|
||||
span.badge-icon
|
||||
i.fa.fa-file-text-o
|
||||
if getVoteQuestion
|
||||
.badge.badge-state-image-only(title=getVoteQuestion)
|
||||
span.badge-icon(class="{{#if voteState}}text-green{{/if}}")
|
||||
i.fa.fa-thumbs-up
|
||||
span.badge-text {{ voteCountPositive }}
|
||||
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}")
|
||||
i.fa.fa-thumbs-down
|
||||
span.badge-text {{ voteCountNegative }}
|
||||
if getPokerQuestion
|
||||
.badge.badge-state-image-only(title=getPokerQuestion)
|
||||
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}")
|
||||
i.fa.fa-check-square
|
||||
if expiredPoker
|
||||
span.badge-text {{ getPokerEstimation }}
|
||||
if attachments.length
|
||||
if allowsBadgeAttachmentOnMinicard
|
||||
.badge
|
||||
span.badge-icon
|
||||
i.fa.fa-paperclip
|
||||
span.badge-text= attachments.length
|
||||
if checklists.length
|
||||
if allowsChecklists
|
||||
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
|
||||
span.badge-icon
|
||||
i.fa.fa-check
|
||||
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
|
||||
if allSubtasks.count
|
||||
if allowsSubtasks
|
||||
.badge
|
||||
span.badge-icon
|
||||
i.fa.fa-globe
|
||||
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
|
||||
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
|
||||
if allowsCardSortingByNumber
|
||||
if allowsCardSortingByNumberOnMinicard
|
||||
.badge
|
||||
span.badge-icon
|
||||
i.fa.fa-sort-numeric-asc
|
||||
span.badge-text.check-list-sort {{ sort }}
|
||||
if shouldShowListOnMinicard
|
||||
.minicard-list-name
|
||||
span
|
||||
i.fa.fa-list
|
||||
span
|
||||
| {{ listName }}
|
||||
if $eq 'subtext-with-full-path' presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentString ' > ' }}
|
||||
if $eq 'subtext-with-parent' presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentCardName }}
|
||||
if allowsDescriptionTextOnMinicard
|
||||
if getDescription
|
||||
.minicard-description
|
||||
+viewer
|
||||
| {{ getDescription }}
|
||||
if shouldShowListOnMinicard
|
||||
.minicard-list-name
|
||||
i.fa.fa-list
|
||||
| {{ listName }}
|
||||
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentString ' > ' }}
|
||||
if $eq 'subtext-with-parent' currentBoard.presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentCardName }}
|
||||
|
||||
template(name="editCardSortOrderPopup")
|
||||
input.js-edit-card-sort-popup(type='text' autofocus value=sort dir="auto")
|
||||
input.js-edit-card-sort-popup(type='text' value=sort dir="auto")
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}
|
||||
|
||||
template(name="minicardChecklist")
|
||||
.minicard-checklist
|
||||
.checklist-header
|
||||
.checklist-title= checklist.title
|
||||
if canModifyCard
|
||||
a.checklist-menu.js-open-checklist-menu(title="{{_ 'checklistActionsPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
each visibleItems
|
||||
+checklistItemDetail(item = . checklist = checklist card = card)
|
||||
|
||||
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}
|
||||
|
|
@ -13,6 +13,24 @@ BlazeComponent.extendComponent({
|
|||
return 'minicard';
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
// cannot be done with CSS because newlines
|
||||
// rendered by the JADE engine count as non empty
|
||||
// and some "empty" divs are nested
|
||||
// this is not very robust and could probably be
|
||||
// done with a helper, but it could be in fact worse
|
||||
// because we would need to to if (allowsX() && X() && ...)
|
||||
const body = $(this.find('.minicard-body'));
|
||||
if (!body) {return}
|
||||
let emptyChildren;
|
||||
do {
|
||||
emptyChildren = body.find('*').filter((_, e) => !e.classList.contains('fa') && $(e).html().trim().length === 0).remove();
|
||||
} while (emptyChildren.length > 0)
|
||||
if (body.html().trim().length === 0) {
|
||||
body.parent().find('hr:has(+ .minicard-body)').remove();
|
||||
}
|
||||
},
|
||||
|
||||
formattedCurrencyCustomFieldValue(definition) {
|
||||
const customField = this.data()
|
||||
.customFieldsWD()
|
||||
|
|
@ -39,46 +57,14 @@ BlazeComponent.extendComponent({
|
|||
return ret;
|
||||
},
|
||||
|
||||
showCreatorOnMinicard() {
|
||||
// cache "board" to reduce the mini-mongodb access
|
||||
const board = this.data().board();
|
||||
let ret = false;
|
||||
if (board) {
|
||||
ret = board.allowsCreatorOnMinicard ?? false;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
isWatching() {
|
||||
const card = this.currentData();
|
||||
return card.findWatcher(Meteor.userId());
|
||||
},
|
||||
|
||||
showMembers() {
|
||||
// cache "board" to reduce the mini-mongodb access
|
||||
const board = this.data().board();
|
||||
let ret = false;
|
||||
if (board) {
|
||||
ret =
|
||||
board.allowsMembers === null ||
|
||||
board.allowsMembers === undefined ||
|
||||
board.allowsMembers
|
||||
;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
showAssignee() {
|
||||
// cache "board" to reduce the mini-mongodb access
|
||||
const board = this.data().board();
|
||||
let ret = false;
|
||||
if (board) {
|
||||
ret =
|
||||
board.allowsAssignee === null ||
|
||||
board.allowsAssignee === undefined ||
|
||||
board.allowsAssignee
|
||||
;
|
||||
}
|
||||
return ret;
|
||||
isSelected() {
|
||||
const card = this.currentData();
|
||||
return Session.get('currentCard') === card._id;
|
||||
},
|
||||
|
||||
/** opens the card label popup only if clicked onto a label
|
||||
|
|
@ -87,6 +73,8 @@ BlazeComponent.extendComponent({
|
|||
*/
|
||||
cardLabelsPopup(event) {
|
||||
if (this.find('.js-card-label:hover')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: this.currentData()});
|
||||
}
|
||||
},
|
||||
|
|
@ -203,7 +191,7 @@ BlazeComponent.extendComponent({
|
|||
visibleItems() {
|
||||
const checklist = this.currentData().checklist || this.currentData();
|
||||
const items = checklist.items();
|
||||
|
||||
|
||||
return items.filter(item => {
|
||||
// Hide finished items if hideCheckedChecklistItems is true
|
||||
if (item.isFinished && checklist.hideCheckedChecklistItems) {
|
||||
|
|
@ -254,33 +242,8 @@ Template.minicard.helpers({
|
|||
},
|
||||
|
||||
shouldShowListOnMinicard() {
|
||||
// Show list name if either:
|
||||
// 1. Board-wide setting is enabled, OR
|
||||
// 2. This specific card has the setting enabled
|
||||
const currentBoard = this.board();
|
||||
if (!currentBoard) return false;
|
||||
return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard;
|
||||
return Utils.allowsShowLists();
|
||||
},
|
||||
|
||||
shouldShowChecklistAtMinicard() {
|
||||
// Return checklists that should be shown on minicard
|
||||
const currentBoard = this.board();
|
||||
if (!currentBoard) return [];
|
||||
|
||||
const checklists = this.checklists();
|
||||
const visibleChecklists = [];
|
||||
|
||||
checklists.forEach(checklist => {
|
||||
// Show checklist if either:
|
||||
// 1. Board-wide setting is enabled, OR
|
||||
// 2. This specific checklist has the setting enabled
|
||||
if (currentBoard.allowsChecklistAtMinicard || checklist.showChecklistAtMinicard) {
|
||||
visibleChecklists.push(checklist);
|
||||
}
|
||||
});
|
||||
|
||||
return visibleChecklists;
|
||||
}
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
.result-card-context-list {
|
||||
margin-bottom: 0.7rem;
|
||||
display: flex;
|
||||
gap: 0.2ch;
|
||||
}
|
||||
.result-card-block-wrapper {
|
||||
display: inline-block;
|
||||
|
|
|
|||
|
|
@ -14,17 +14,10 @@ BlazeComponent.extendComponent({
|
|||
onReady() {
|
||||
Session.set('popupCardId', cardId);
|
||||
Session.set('popupCardBoardId', boardId);
|
||||
this_.cardDetailsPopup(evt);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
cardDetailsPopup(event) {
|
||||
if (!Popup.isOpen()) {
|
||||
Popup.open("cardDetails")(event);
|
||||
}
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,19 +4,14 @@
|
|||
textarea.js-add-subtask-item,
|
||||
textarea.js-edit-subtask-item {
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
resize: none;
|
||||
height: 34px;
|
||||
}
|
||||
.delete-text,
|
||||
.subtask-title .js-delete-subtask,
|
||||
.subtask-title .js-view-subtask,
|
||||
.js-delete-subtask-item {
|
||||
color: #8c8c8c;
|
||||
text-decoration: underline;
|
||||
word-wrap: break-word;
|
||||
float: right;
|
||||
padding-top: 6px;
|
||||
}
|
||||
.delete-text:hover,
|
||||
.subtask-title .js-delete-subtask:hover,
|
||||
|
|
@ -28,11 +23,11 @@ textarea.js-edit-subtask-item {
|
|||
float: left;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 18px;
|
||||
|
||||
line-height: 30px;
|
||||
}
|
||||
.subtask-title .title {
|
||||
font-size: 18px;
|
||||
|
||||
line-height: 25px;
|
||||
}
|
||||
.subtask-title .subtasks-stat {
|
||||
|
|
@ -133,7 +128,7 @@ textarea.js-edit-subtask-item {
|
|||
margin: 0.1em 0 0 0;
|
||||
}
|
||||
.subtasks-item .check-box.is-checked {
|
||||
border-bottom: 2px solid #3cb500;
|
||||
border-bottom: 0.2ch solid #3cb500;
|
||||
border-right: 2px solid #3cb500;
|
||||
}
|
||||
/* Unicode checkbox icons styling */
|
||||
|
|
@ -165,16 +160,4 @@ body.grey-icons-enabled .subtasks-item .check-box-unicode {
|
|||
}
|
||||
.subtasks-item .item-title .viewer p {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.js-delete-subtask-item {
|
||||
margin: 0 0 0.5em 1.33em;
|
||||
padding: 12px 0 0 0;
|
||||
}
|
||||
.add-subtask-item {
|
||||
margin: 0.2em 0 0.5em 1.33em;
|
||||
display: inline-block;
|
||||
}
|
||||
.subtask-details-menu {
|
||||
float: right;
|
||||
padding: 6px 10px 6px 10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,20 @@ template(name="subtasks")
|
|||
| {{_ 'subtasks'}}
|
||||
if currentUser.isBoardAdmin
|
||||
if toggleDeleteDialog.get
|
||||
.board-overlay#card-details-overlay
|
||||
+subtaskDeleteDialog(subtask = subtaskToDelete)
|
||||
|
||||
.card-subtasks-items
|
||||
each subtask in currentCard.subtasks
|
||||
+subtaskDetail(subtask = subtask)
|
||||
if currentCard.subtasks
|
||||
.card-subtasks-items
|
||||
each subtask in currentCard.subtasks
|
||||
.subtask-container
|
||||
+subtaskDetail(subtask = subtask)
|
||||
if canModifyCard
|
||||
a.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
|
||||
| ☰
|
||||
if currentUser.isBoardAdmin
|
||||
a.js-delete-subtask-item
|
||||
| ❌
|
||||
|
||||
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-subtask" cardId = cardId)
|
||||
|
|
@ -24,9 +32,6 @@ template(name="subtaskDetail")
|
|||
+editSubtaskItemForm(subtask = subtask)
|
||||
else
|
||||
.subtask-title
|
||||
span
|
||||
if canModifyCard
|
||||
a.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
|
||||
if canModifyCard
|
||||
h2.title.js-open-inlined-form.is-editable
|
||||
+viewer
|
||||
|
|
@ -37,13 +42,13 @@ template(name="subtaskDetail")
|
|||
= subtask.title
|
||||
|
||||
template(name="addSubtaskItemForm")
|
||||
textarea.js-add-subtask-item(rows='1' autofocus dir="auto")
|
||||
textarea.js-add-subtask-item(rows='1' dir="auto")
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-submit-add-subtask-item-form(type="submit") {{_ 'save'}}
|
||||
a.js-close-inlined-form
|
||||
|
||||
template(name="editSubtaskItemForm")
|
||||
textarea.js-edit-subtask-item(rows='1' autofocus dir="auto")
|
||||
textarea.js-edit-subtask-item(rows='1' dir="auto")
|
||||
if $eq type 'item'
|
||||
= item.title
|
||||
else
|
||||
|
|
@ -52,9 +57,6 @@ template(name="editSubtaskItemForm")
|
|||
button.primary.confirm.js-submit-edit-subtask-item-form(type="submit") {{_ 'save'}}
|
||||
a.js-close-inlined-form
|
||||
span(title=createdAt) {{ moment createdAt }}
|
||||
if canModifyCard
|
||||
if currentUser.isBoardAdmin
|
||||
a.js-delete-subtask-item {{_ "delete"}}...
|
||||
|
||||
template(name="subtasksItems")
|
||||
.subtasks-items.js-subtasks-items
|
||||
|
|
@ -100,4 +102,3 @@ template(name="subtaskActionsPopup")
|
|||
a.js-delete-subtask.delete-subtask
|
||||
i.fa.fa-trash
|
||||
| {{_ "delete"}} ...
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
.original-position-info {
|
||||
margin: 5px 0;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
border-radius: 0.4ch;
|
||||
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
|
||||
.original-title {
|
||||
color: #6c757d;
|
||||
font-size: 11px;
|
||||
|
||||
margin-top: 4px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
|
|
@ -78,14 +78,14 @@
|
|||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.original-position-info {
|
||||
font-size: 11px;
|
||||
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
|
||||
.original-position-details {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
|
||||
.original-position-moved,
|
||||
.original-position-unchanged {
|
||||
padding: 3px 5px;
|
||||
|
|
@ -99,24 +99,24 @@
|
|||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.original-position-moved {
|
||||
background-color: #744210;
|
||||
border-color: #b7791f;
|
||||
color: #fbd38d;
|
||||
}
|
||||
|
||||
|
||||
.original-position-unchanged {
|
||||
background-color: #22543d;
|
||||
border-color: #38a169;
|
||||
color: #9ae6b4;
|
||||
}
|
||||
|
||||
|
||||
.original-title {
|
||||
color: #a0aec0;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
|
||||
.original-title strong {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,18 @@
|
|||
.datepicker-container .fields .left {
|
||||
width: 56%;
|
||||
}
|
||||
.datepicker-container .fields .right {
|
||||
width: 38%;
|
||||
}
|
||||
.datepicker-container .datepicker {
|
||||
width: 100%;
|
||||
}
|
||||
.datepicker-container .datepicker table {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.datepicker-container .datepicker table thead {
|
||||
background: none;
|
||||
}
|
||||
.datepicker-container .datepicker table td,
|
||||
.datepicker-container .datepicker table th {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.datepicker-container {
|
||||
form {
|
||||
display: flex;
|
||||
gap: 0.3lh;
|
||||
padding: 0.3lh 1ch;
|
||||
}
|
||||
.fields {
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
gap: 1ch;
|
||||
.left, .right {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,16 @@
|
|||
select, button, input {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
form {
|
||||
/* 🛑 remove me if it causes a significant issue.
|
||||
this can be overidden and otherwise allow forms to
|
||||
scale with their parent. */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
select,
|
||||
textarea,
|
||||
input:not([type=file]),
|
||||
|
|
@ -7,9 +20,8 @@ button {
|
|||
border: 1px solid #ccc;
|
||||
border-radius: 0.4vw;
|
||||
display: block;
|
||||
margin-bottom: 1.5vh;
|
||||
min-height: 4.5vh;
|
||||
padding: 1vh 1vw;
|
||||
padding: 0.3lh 1ch;
|
||||
max-width: clamp(30vw, 100%, 800px);
|
||||
}
|
||||
select.full,
|
||||
textarea.full,
|
||||
|
|
@ -42,18 +54,6 @@ input[type="text"],
|
|||
input[type="password"],
|
||||
input[type="email"] {
|
||||
transition: background 85ms ease-in, border-color 85ms ease-in;
|
||||
width: min(250px, 30vw);
|
||||
}
|
||||
input[type="text"].inline-input,
|
||||
input[type="password"].inline-input,
|
||||
input[type="email"].inline-input {
|
||||
background: none;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0.3vh;
|
||||
min-height: 0;
|
||||
height: 2.5vh;
|
||||
width: min(200px, 25vw);
|
||||
}
|
||||
input[type="text"].full-line,
|
||||
input[type="password"].full-line,
|
||||
|
|
@ -102,11 +102,6 @@ textarea:disabled {
|
|||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
select {
|
||||
max-height: 40vh;
|
||||
width: min(256px, 32vw);
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
select.inline {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -114,14 +109,11 @@ option[disabled] {
|
|||
color: #222;
|
||||
}
|
||||
textarea {
|
||||
height: 20vh;
|
||||
transition: background 85ms ease-in, border-color 85ms ease-in;
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
}
|
||||
textarea.editor {
|
||||
resize: none;
|
||||
padding-bottom: 3vh;
|
||||
width: auto;
|
||||
font-size: 0.9em;
|
||||
min-height: 3lh;
|
||||
}
|
||||
.button {
|
||||
border-radius: 3px;
|
||||
|
|
@ -137,9 +129,16 @@ button {
|
|||
display: inline-block;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
padding: 1vh 2.5vw;
|
||||
/* in flex layouts, padding often disturbs computations. rather rarely have
|
||||
two lines, so setting relative-unit min-height works better */
|
||||
min-height: 1.8lh;
|
||||
padding: 0 2ch;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
z-index: 1;
|
||||
:not(.password-toggle-btn) {
|
||||
margin-top: 0.1lh;
|
||||
}
|
||||
}
|
||||
input[type="submit"] .wide,
|
||||
button .wide {
|
||||
|
|
@ -241,9 +240,9 @@ input[type="hidden"] {
|
|||
}
|
||||
.radio-div,
|
||||
.check-div {
|
||||
display: block;
|
||||
margin: 0 0 0.5vh 2.5vw;
|
||||
min-height: 2.5vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.2lh;
|
||||
position: relative;
|
||||
}
|
||||
.radio-div input,
|
||||
|
|
@ -260,9 +259,10 @@ input[type="hidden"] {
|
|||
font-weight: 400;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5vh;
|
||||
}
|
||||
label.form-error {
|
||||
color: #d32f2f;
|
||||
|
|
@ -274,11 +274,32 @@ textarea::-moz-placeholder {
|
|||
color: #333 !important;
|
||||
}
|
||||
.edit-controls,
|
||||
.add-controls {
|
||||
.add-controls,
|
||||
.links-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 1.5vh;
|
||||
gap: 1ch;
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-controls {
|
||||
grid-area: main-controls;
|
||||
}
|
||||
|
||||
.add-controls {
|
||||
grid-area: main-controls;
|
||||
}
|
||||
|
||||
.links-controls {
|
||||
grid-area: links-controls
|
||||
}
|
||||
|
||||
.links-controls span.quiet {
|
||||
margin: auto;
|
||||
}
|
||||
@media print {
|
||||
.add-controls {
|
||||
|
|
@ -289,14 +310,7 @@ textarea::-moz-placeholder {
|
|||
.add-controls button[type=submit],
|
||||
.edit-controls input[type=button],
|
||||
.add-controls input[type=button] {
|
||||
float: left;
|
||||
height: 4.5vh;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.edit-controls .fa-times-thin,
|
||||
.add-controls .fa-times-thin {
|
||||
font-size: clamp(20px, 4vw, 26px);
|
||||
margin: 0.5vh 1.5vw;
|
||||
margin: 0;
|
||||
}
|
||||
[type="checkbox"]:not(:checked),
|
||||
[type="checkbox"]:checked {
|
||||
|
|
@ -306,6 +320,18 @@ textarea::-moz-placeholder {
|
|||
display: none;
|
||||
}
|
||||
.materialCheckBox {
|
||||
position: relative;
|
||||
width: 0.5lh;
|
||||
height: 0.5lh;
|
||||
z-index: 0;
|
||||
border: 0.2ch solid #5a5a5a;
|
||||
border-radius: 1px;
|
||||
transition: 0.2s;
|
||||
margin: 0;
|
||||
margin-left: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.materialCheckBox:is(.active) {
|
||||
position: relative;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
|
|
@ -317,19 +343,33 @@ textarea::-moz-placeholder {
|
|||
cursor: pointer;
|
||||
}
|
||||
.materialCheckBox.is-checked {
|
||||
top: -4px;
|
||||
left: -3px;
|
||||
width: 7px;
|
||||
height: 15px;
|
||||
margin-right: 6px;
|
||||
border-top: 2px solid transparent;
|
||||
border-left: 2px solid transparent;
|
||||
border-bottom: 2px solid #3cb500;
|
||||
border-right: 2px solid #3cb500;
|
||||
transform: rotate(40deg);
|
||||
top: 0.3lh;
|
||||
left: 0.25lh;
|
||||
width: 0.25lh;
|
||||
height: 0.5lh;
|
||||
margin-right: 0.6lh;
|
||||
border-top: 0 solid transparent;
|
||||
border-left: 0 solid transparent;
|
||||
border-bottom: 0.3ch solid #3cb500;
|
||||
border-right: 0.3ch solid #3cb500;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
transform-origin: 100% 100%;
|
||||
transform: rotate(50deg);
|
||||
backface-visibility: hidden;
|
||||
transform-origin: 0.5lh 0;
|
||||
}
|
||||
|
||||
form .form-buttons {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
justify-content: stretch;
|
||||
gap: 0.5ch;
|
||||
&>button {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
/* Grey checkmarks when grey icons setting is enabled */
|
||||
body.grey-icons-enabled .materialCheckBox.is-checked {
|
||||
|
|
@ -362,7 +402,7 @@ body.grey-icons-enabled .materialCheckBox.is-checked {
|
|||
border-radius: 3px;
|
||||
color: #fff;
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
|
||||
font-weight: 700;
|
||||
height: 17px;
|
||||
line-height: 17px;
|
||||
|
|
@ -419,7 +459,7 @@ body.grey-icons-enabled .materialCheckBox.is-checked {
|
|||
.button-link.setting .label {
|
||||
color: #222;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
|
||||
line-height: 14px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -428,7 +468,7 @@ body.grey-icons-enabled .materialCheckBox.is-checked {
|
|||
}
|
||||
.button-link.setting .value {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
|
||||
line-height: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
@ -572,7 +612,7 @@ button.loud-text-button:hover {
|
|||
padding: 11px;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
|
||||
line-height: 20px;
|
||||
}
|
||||
.big-link .text {
|
||||
|
|
@ -615,7 +655,7 @@ button.loud-text-button:hover {
|
|||
width: 40px;
|
||||
}
|
||||
.big-link.avatar-changer .member .member-initials {
|
||||
font-size: 16px;
|
||||
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
max-height: 40px;
|
||||
|
|
@ -655,7 +695,7 @@ button.loud-text-button:hover {
|
|||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
font-size: 23px;
|
||||
|
||||
}
|
||||
.uploader .realfile input[type="file"] {
|
||||
cursor: pointer;
|
||||
|
|
@ -666,7 +706,7 @@ button.loud-text-button:hover {
|
|||
padding: 0;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
font-size: 23px;
|
||||
|
||||
}
|
||||
.uploader:hover .fakefile {
|
||||
background: #318ec4;
|
||||
|
|
@ -705,13 +745,13 @@ button.loud-text-button:hover {
|
|||
color: #fff;
|
||||
}
|
||||
.material-toggle-switch {
|
||||
display: flex;
|
||||
padding: 0.2rlh 1ch;
|
||||
align-self: center;
|
||||
}
|
||||
.toggle-label {
|
||||
height: 0.6rlh;
|
||||
width: 1.3rlh;
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 44px;
|
||||
background-color: #a6a6a6;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
|
|
@ -719,11 +759,13 @@ button.loud-text-button:hover {
|
|||
}
|
||||
.toggle-label:after {
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
top: -3px;
|
||||
display: block;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
/* ensure vertical centering */
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -0.2rlh;
|
||||
width: .8rlh;
|
||||
height: .8rlh;
|
||||
border-radius: 100px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.05);
|
||||
|
|
@ -737,7 +779,7 @@ button.loud-text-button:hover {
|
|||
background-color: #6fbeb5;
|
||||
}
|
||||
.toggle-switch:checked ~ .toggle-label:after {
|
||||
left: 20px;
|
||||
left: 1.5ch;
|
||||
background-color: #179588;
|
||||
}
|
||||
.toggle-switch:checked:disabled ~ .toggle-label {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
min-width: 800px;
|
||||
border: 2px solid #666;
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
padding: 2px 1px; /* half */
|
||||
text-align: center;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 11px;
|
||||
|
||||
min-width: 15px; /* half of 30px */
|
||||
font-weight: bold;
|
||||
height: auto;
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
vertical-align: middle;
|
||||
line-height: 28px;
|
||||
background-color: #ffffff;
|
||||
font-size: 18px;
|
||||
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
.gantt-container tbody td.ganttview-block {
|
||||
background-color: #4CAF50 !important;
|
||||
color: #fff !important;
|
||||
font-size: 18px !important;
|
||||
|
||||
font-weight: bold !important;
|
||||
padding: 2px !important;
|
||||
border-radius: 2px;
|
||||
|
|
@ -171,7 +171,7 @@
|
|||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.gantt-container table {
|
||||
font-size: 11px;
|
||||
|
||||
}
|
||||
|
||||
.gantt-container thead td {
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
|
||||
.gantt-container tbody td:first-child {
|
||||
width: 100px;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,13 @@
|
|||
template(name='list')
|
||||
.list.js-list(id="js-list-{{_id}}"
|
||||
style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}"
|
||||
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
+listHeader
|
||||
unless collapsed
|
||||
+listBody
|
||||
.list-resize-handle.js-list-resize-handle.nodragscroll
|
||||
.list-resize-handle.js-list-resize-handle.nodragscroll
|
||||
|
||||
template(name='miniList')
|
||||
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
+listHeader
|
||||
if isCurrentList
|
||||
+listBody
|
||||
|
|
@ -4,6 +4,8 @@ require('/client/lib/jquery-ui.js')
|
|||
|
||||
const { calculateIndex } = Utils;
|
||||
|
||||
export const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
// Proxy
|
||||
openForm(options) {
|
||||
|
|
@ -12,6 +14,7 @@ BlazeComponent.extendComponent({
|
|||
|
||||
onCreated() {
|
||||
this.newCardFormIsVisible = new ReactiveVar(true);
|
||||
this.collapse = new ReactiveVar(Utils.getListCollapseState(this.data()));
|
||||
},
|
||||
|
||||
// The jquery UI sortable library is the best solution I've found so far. I
|
||||
|
|
@ -22,183 +25,37 @@ BlazeComponent.extendComponent({
|
|||
// callback, we basically solve all issues related to reactive updates. A
|
||||
// comment below provides further details.
|
||||
onRendered() {
|
||||
const boardComponent = this.parentComponent().parentComponent();
|
||||
|
||||
// Initialize list resize functionality immediately
|
||||
this.list = this.firstNode();
|
||||
this.resizeHandle = this.find('.js-list-resize-handle');
|
||||
this.initializeListResize();
|
||||
|
||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
const $cards = this.$('.js-minicards');
|
||||
|
||||
$cards.sortable({
|
||||
connectWith: '.js-minicards:not(.js-list-full)',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper(evt, item) {
|
||||
const helper = item.clone();
|
||||
if (MultiSelection.isActive()) {
|
||||
const andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
|
||||
if (andNOthers > 0) {
|
||||
helper.append(
|
||||
$(
|
||||
Blaze.toHTML(
|
||||
HTML.DIV(
|
||||
{ class: 'and-n-other' },
|
||||
TAPi18n.__('and-n-other-card', { count: andNOthers }),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return helper;
|
||||
},
|
||||
distance: 7,
|
||||
items: itemsSelector,
|
||||
placeholder: 'minicard-wrapper placeholder',
|
||||
scrollSpeed: 10,
|
||||
start(evt, ui) {
|
||||
ui.helper.css('z-index', 1000);
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
boardComponent.setIsDragging(true);
|
||||
},
|
||||
stop(evt, ui) {
|
||||
// To attribute the new index number, we need to get the DOM element
|
||||
// of the previous and the following card -- if any.
|
||||
const prevCardDom = ui.item.prev('.js-minicard').get(0);
|
||||
const nextCardDom = ui.item.next('.js-minicard').get(0);
|
||||
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
|
||||
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
|
||||
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id;
|
||||
let targetSwimlaneId = null;
|
||||
|
||||
// only set a new swimelane ID if the swimlanes view is active
|
||||
if (
|
||||
Utils.boardView() === 'board-view-swimlanes' ||
|
||||
currentBoard.isTemplatesBoard()
|
||||
)
|
||||
targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))
|
||||
._id;
|
||||
|
||||
// Normally the jquery-ui sortable library moves the dragged DOM element
|
||||
// to its new position, which disrupts Blaze reactive updates mechanism
|
||||
// (especially when we move the last card of a list, or when multiple
|
||||
// users move some cards at the same time). To prevent these UX glitches
|
||||
// we ask sortable to gracefully cancel the move, and to put back the
|
||||
// DOM in its initial state. The card move is then handled reactively by
|
||||
// Blaze with the below query.
|
||||
$cards.sortable('cancel');
|
||||
|
||||
if (MultiSelection.isActive()) {
|
||||
ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] }).forEach((card, i) => {
|
||||
const newSwimlaneId = targetSwimlaneId
|
||||
? targetSwimlaneId
|
||||
: card.swimlaneId || defaultSwimlaneId;
|
||||
card.move(
|
||||
currentBoard._id,
|
||||
newSwimlaneId,
|
||||
listId,
|
||||
sortIndex.base + i * sortIndex.increment,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const cardDomElement = ui.item.get(0);
|
||||
const card = Blaze.getData(cardDomElement);
|
||||
const newSwimlaneId = targetSwimlaneId
|
||||
? targetSwimlaneId
|
||||
: card.swimlaneId || defaultSwimlaneId;
|
||||
card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base);
|
||||
}
|
||||
boardComponent.setIsDragging(false);
|
||||
},
|
||||
sort(event, ui) {
|
||||
const $boardCanvas = $('.board-canvas');
|
||||
const boardCanvas = $boardCanvas[0];
|
||||
|
||||
if (event.pageX < 10) { // scroll to the left
|
||||
boardCanvas.scrollLeft -= 15;
|
||||
ui.helper[0].offsetLeft -= 15;
|
||||
}
|
||||
if (
|
||||
event.pageX > boardCanvas.offsetWidth - 10 &&
|
||||
boardCanvas.scrollLeft < $boardCanvas.data('scrollLeftMax') // don't scroll more than possible
|
||||
) { // scroll to the right
|
||||
boardCanvas.scrollLeft += 15;
|
||||
}
|
||||
if (
|
||||
event.pageY > boardCanvas.offsetHeight - 10 &&
|
||||
event.pageY + boardCanvas.scrollTop < $boardCanvas.data('scrollTopMax') // don't scroll more than possible
|
||||
) { // scroll to the bottom
|
||||
boardCanvas.scrollTop += 15;
|
||||
}
|
||||
if (event.pageY < 10) { // scroll to the top
|
||||
boardCanvas.scrollTop -= 15;
|
||||
}
|
||||
},
|
||||
activate(event, ui) {
|
||||
const $boardCanvas = $('.board-canvas');
|
||||
const boardCanvas = $boardCanvas[0];
|
||||
// scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax)
|
||||
// https://www.it-swarm.com.de/de/javascript/so-erhalten-sie-den-maximalen-dokument-scrolltop-wert/1069126844/
|
||||
$boardCanvas.data('scrollTopMax', boardCanvas.scrollHeight - boardCanvas.clientTop);
|
||||
// https://stackoverflow.com/questions/5138373/how-do-i-get-the-max-value-of-scrollleft/5704386#5704386
|
||||
$boardCanvas.data('scrollLeftMax', boardCanvas.scrollWidth - boardCanvas.clientWidth);
|
||||
},
|
||||
});
|
||||
|
||||
this.autorun(() => {
|
||||
if ($cards.data('uiSortable') || $cards.data('sortable')) {
|
||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
$cards.sortable('option', 'handle', '.handle');
|
||||
} else {
|
||||
$cards.sortable('option', 'handle', '.minicard');
|
||||
}
|
||||
|
||||
$cards.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
// Disable drag-dropping when user is not member
|
||||
!Utils.canModifyBoard(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !Utils.canModifyBoard(),
|
||||
);
|
||||
const ensureCollapseState = (collapsed) => {
|
||||
if (this.collapse.get() === collapsed) return;
|
||||
if (this.autoWidth() || collapsed) {
|
||||
$(this.resizeHandle).hide();
|
||||
} else {
|
||||
$(this.resizeHandle).show();
|
||||
}
|
||||
});
|
||||
this.collapse.set(collapsed);
|
||||
this.initializeListResize();
|
||||
}
|
||||
|
||||
// We want to re-run this function any time a card is added.
|
||||
// Reactively update collapse appearance and resize handle visibility when auto-width or collapse changes
|
||||
this.autorun(() => {
|
||||
const currentBoardId = Tracker.nonreactive(() => {
|
||||
return Session.get('currentBoard');
|
||||
});
|
||||
Tracker.afterFlush(() => {
|
||||
$cards.find(itemsSelector).droppable({
|
||||
hoverClass: 'draggable-hover-card',
|
||||
accept: '.js-member,.js-label',
|
||||
drop(event, ui) {
|
||||
const cardId = Blaze.getData(this)._id;
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
|
||||
if (ui.draggable.hasClass('js-member')) {
|
||||
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
||||
card.assignMember(memberId);
|
||||
} else {
|
||||
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
||||
card.addLabel(labelId);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
ensureCollapseState(Utils.getListCollapseState(this.data()));
|
||||
});
|
||||
},
|
||||
|
||||
collapsed() {
|
||||
return this.collapse.get();
|
||||
},
|
||||
|
||||
|
||||
listWidth() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
if (!list) return 270; // Return default width if list is not available
|
||||
|
||||
|
||||
if (user) {
|
||||
// For logged-in users, get from user profile
|
||||
return user.getListWidthFromStorage(list.boardId, list._id);
|
||||
|
|
@ -222,8 +79,8 @@ BlazeComponent.extendComponent({
|
|||
listConstraint() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
if (!list) return 550; // Return default constraint if list is not available
|
||||
|
||||
if (!list) return 0;
|
||||
|
||||
if (user) {
|
||||
// For logged-in users, get from user profile
|
||||
return user.getListConstraintFromStorage(list.boardId, list._id);
|
||||
|
|
@ -240,7 +97,7 @@ BlazeComponent.extendComponent({
|
|||
} catch (e) {
|
||||
console.warn('Error reading list constraint from localStorage:', e);
|
||||
}
|
||||
return 550; // Return default constraint if not found
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -256,18 +113,14 @@ BlazeComponent.extendComponent({
|
|||
|
||||
initializeListResize() {
|
||||
// Check if we're still in a valid template context
|
||||
if (!Template.currentData()) {
|
||||
if (!this.data()) {
|
||||
console.warn('No current template data available for list resize initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
const list = Template.currentData();
|
||||
const $list = this.$('.js-list');
|
||||
const $resizeHandle = this.$('.js-list-resize-handle');
|
||||
|
||||
|
||||
// Check if elements exist
|
||||
if (!$list.length || !$resizeHandle.length) {
|
||||
console.warn('List or resize handle not found, retrying in 100ms');
|
||||
if (!this.list || !this.resizeHandle) {
|
||||
console.info('List or resize handle not found, retrying in 100ms');
|
||||
Meteor.setTimeout(() => {
|
||||
if (!this.isDestroyed) {
|
||||
this.initializeListResize();
|
||||
|
|
@ -275,107 +128,129 @@ BlazeComponent.extendComponent({
|
|||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reactively show/hide resize handle based on collapse and auto-width state
|
||||
this.autorun(() => {
|
||||
const isAutoWidth = this.autoWidth();
|
||||
const isCollapsed = Utils.getListCollapseState(list);
|
||||
if (isCollapsed || isAutoWidth) {
|
||||
$resizeHandle.hide();
|
||||
} else {
|
||||
$resizeHandle.show();
|
||||
}
|
||||
});
|
||||
|
||||
let isResizing = false;
|
||||
let startX = 0;
|
||||
let startWidth = 0;
|
||||
let minWidth = 270; // Minimum width matching system default
|
||||
let listConstraint = this.listConstraint(); // Store constraint value for use in event handlers
|
||||
const component = this; // Store reference to component for use in event handlers
|
||||
let previousLimit = false;
|
||||
// seems reasonable; better let user shrink too much that too little
|
||||
const minWidth = 280;
|
||||
// stored width
|
||||
const width = this.listWidth();
|
||||
// min-width is initially min-content; a good start
|
||||
let maxWidth = this.listConstraint() || parseInt(this.list.style.getProperty('--list-min-width', `${(minWidth)}px`), 10) || width + 100;
|
||||
if (!width || width > maxWidth) {
|
||||
width = (maxWidth + minWidth) / 2;
|
||||
}
|
||||
|
||||
this.list.style.setProperty('--list-min-width', `${Math.round(minWidth)}px`);
|
||||
// actual size before fitting (usually max-content equivalent)
|
||||
this.list.style.setProperty('--list-max-width', `${Math.round(maxWidth)}px`);
|
||||
// avoid jump effect and ensure width stays consistent
|
||||
this.list.style.setProperty('--list-width', `${Math.round(width)}px`);
|
||||
|
||||
const component = this;
|
||||
|
||||
// wait for click to add other events
|
||||
const startResize = (e) => {
|
||||
isResizing = true;
|
||||
startX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
startWidth = $list.outerWidth();
|
||||
|
||||
|
||||
// Add visual feedback
|
||||
$list.addClass('list-resizing');
|
||||
$('body').addClass('list-resizing-active');
|
||||
|
||||
|
||||
// Prevent text selection during resize
|
||||
$('body').css('user-select', 'none');
|
||||
|
||||
// gain access to modern attributes e.g. isPrimary
|
||||
e = e.originalEvent;
|
||||
|
||||
if (isResizing || Utils.shouldIgnorePointer(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$(document).on('pointermove', doResize);
|
||||
// e.g. debugger can cancel event without pointerup being fired
|
||||
$(document).on('pointercancel', stopResize);
|
||||
$(document).on('pointerup', stopResize);
|
||||
|
||||
// --list-width can be either a stored size or "auto"; get actual computed size
|
||||
component.currentWidth = component.list.offsetWidth;
|
||||
component.list.classList.add('list-resizing');
|
||||
document.body.classList.add('list-resizing-active');
|
||||
|
||||
isResizing = true;
|
||||
};
|
||||
|
||||
const doResize = (e) => {
|
||||
if (!isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
const deltaX = currentX - startX;
|
||||
const newWidth = Math.max(minWidth, startWidth + deltaX);
|
||||
|
||||
// Apply the new width immediately for real-time feedback
|
||||
$list[0].style.setProperty('--list-width', `${newWidth}px`);
|
||||
$list[0].style.setProperty('width', `${newWidth}px`);
|
||||
$list[0].style.setProperty('min-width', `${newWidth}px`);
|
||||
$list[0].style.setProperty('max-width', `${newWidth}px`);
|
||||
$list[0].style.setProperty('flex', 'none');
|
||||
$list[0].style.setProperty('flex-basis', 'auto');
|
||||
$list[0].style.setProperty('flex-grow', '0');
|
||||
$list[0].style.setProperty('flex-shrink', '0');
|
||||
|
||||
|
||||
e = e.originalEvent;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isResizing || !e.isPrimary) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!previousLimit && component.collapsed()) {
|
||||
previousLimit = true;
|
||||
component.list.classList.add('cannot-resize');
|
||||
return;
|
||||
}
|
||||
|
||||
// relative to document, always >0 because pointer sticks to the right of list
|
||||
const deltaX = e.clientX - component.list.getBoundingClientRect().right;
|
||||
const candidateWidth = component.currentWidth + deltaX;
|
||||
component.currentWidth = Math.max(minWidth, Math.min(maxWidth, candidateWidth));
|
||||
const reachingMax = (maxWidth - component.currentWidth - 20) <= 0
|
||||
const reachingMin = (component.currentWidth - 20 - minWidth) <= 0
|
||||
// visual indicator to avoid trying too hard; try not to apply each tick
|
||||
if (!previousLimit && (reachingMax && deltaX > 0 || reachingMin && deltaX < 0)) {
|
||||
component.list.classList.add('cannot-resize');
|
||||
previousLimit = true;
|
||||
} else if (previousLimit && !reachingMax && !reachingMin) {
|
||||
component.list.classList.remove('cannot-resize');
|
||||
previousLimit = false;
|
||||
}
|
||||
// Apply the new width immediately for real-time feedback
|
||||
component.list.style.setProperty('--list-width', `${component.currentWidth}px`);
|
||||
};
|
||||
|
||||
const stopResize = (e) => {
|
||||
if (!isResizing) return;
|
||||
|
||||
e = e.originalEvent;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isResizing || !e.isPrimary) {
|
||||
return;
|
||||
}
|
||||
|
||||
// hopefully be gentler on cpu
|
||||
$(document).off('pointermove', doResize);
|
||||
$(document).off('pointercancel', stopResize);
|
||||
$(document).off('pointerup', stopResize);
|
||||
isResizing = false;
|
||||
|
||||
// Calculate final width
|
||||
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
const deltaX = currentX - startX;
|
||||
const finalWidth = Math.max(minWidth, startWidth + deltaX);
|
||||
|
||||
// Ensure the final width is applied
|
||||
$list[0].style.setProperty('--list-width', `${finalWidth}px`);
|
||||
$list[0].style.setProperty('width', `${finalWidth}px`);
|
||||
$list[0].style.setProperty('min-width', `${finalWidth}px`);
|
||||
$list[0].style.setProperty('max-width', `${finalWidth}px`);
|
||||
$list[0].style.setProperty('flex', 'none');
|
||||
$list[0].style.setProperty('flex-basis', 'auto');
|
||||
$list[0].style.setProperty('flex-grow', '0');
|
||||
$list[0].style.setProperty('flex-shrink', '0');
|
||||
|
||||
// Remove visual feedback but keep the width
|
||||
$list.removeClass('list-resizing');
|
||||
$('body').removeClass('list-resizing-active');
|
||||
$('body').css('user-select', '');
|
||||
|
||||
// Keep the CSS custom property for persistent width
|
||||
// The CSS custom property will remain on the element to maintain the width
|
||||
|
||||
|
||||
if (previousLimit) {
|
||||
component.list.classList.remove('cannot-resize');
|
||||
}
|
||||
|
||||
const finalWidth = parseInt(component.list.style.getPropertyValue('--list-width'), 10);
|
||||
|
||||
// Remove visual feedback but keep the height
|
||||
component.list.classList.remove('list-resizing');
|
||||
document.body.classList.remove('list-resizing-active');
|
||||
|
||||
if (component.collapse.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the new width using the existing system
|
||||
const list = component.data();
|
||||
const boardId = list.boardId;
|
||||
const listId = list._id;
|
||||
|
||||
|
||||
// Use the new storage method that handles both logged-in and non-logged-in users
|
||||
if (process.env.DEBUG === 'true') {
|
||||
}
|
||||
|
||||
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
// For logged-in users, use server method
|
||||
Meteor.call('applyListWidthToStorage', boardId, listId, finalWidth, listConstraint, (error, result) => {
|
||||
Meteor.call('applyListWidthToStorage', boardId, listId, finalWidth, maxWidth, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Error saving list width:', error);
|
||||
} else {
|
||||
|
|
@ -389,61 +264,37 @@ BlazeComponent.extendComponent({
|
|||
// Save list width
|
||||
const storedWidths = localStorage.getItem('wekan-list-widths');
|
||||
let widths = storedWidths ? JSON.parse(storedWidths) : {};
|
||||
|
||||
|
||||
if (!widths[boardId]) {
|
||||
widths[boardId] = {};
|
||||
}
|
||||
widths[boardId][listId] = finalWidth;
|
||||
|
||||
|
||||
localStorage.setItem('wekan-list-widths', JSON.stringify(widths));
|
||||
|
||||
|
||||
// Save list constraint
|
||||
const storedConstraints = localStorage.getItem('wekan-list-constraints');
|
||||
let constraints = storedConstraints ? JSON.parse(storedConstraints) : {};
|
||||
|
||||
|
||||
if (!constraints[boardId]) {
|
||||
constraints[boardId] = {};
|
||||
}
|
||||
constraints[boardId][listId] = listConstraint;
|
||||
|
||||
|
||||
localStorage.setItem('wekan-list-constraints', JSON.stringify(constraints));
|
||||
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error saving list width/constraint to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// Mouse events
|
||||
$resizeHandle.on('mousedown', startResize);
|
||||
$(document).on('mousemove', doResize);
|
||||
$(document).on('mouseup', stopResize);
|
||||
|
||||
// Touch events for mobile
|
||||
$resizeHandle.on('touchstart', startResize, { passive: false });
|
||||
$(document).on('touchmove', doResize, { passive: false });
|
||||
$(document).on('touchend', stopResize, { passive: false });
|
||||
|
||||
|
||||
// Prevent dragscroll interference
|
||||
$resizeHandle.on('mousedown', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
|
||||
// Reactively update resize handle visibility when auto-width or collapse changes
|
||||
component.autorun(() => {
|
||||
const collapsed = Utils.getListCollapseState(list);
|
||||
if (component.autoWidth() || collapsed) {
|
||||
$resizeHandle.hide();
|
||||
} else {
|
||||
$resizeHandle.show();
|
||||
}
|
||||
});
|
||||
// handle both pointer and touch
|
||||
$(this.resizeHandle).on("pointerdown", startResize);
|
||||
|
||||
// Clean up on component destruction
|
||||
component.onDestroyed(() => {
|
||||
|
|
@ -455,12 +306,6 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('list');
|
||||
|
||||
Template.list.helpers({
|
||||
collapsed() {
|
||||
return Utils.getListCollapseState(this);
|
||||
},
|
||||
});
|
||||
|
||||
Template.miniList.events({
|
||||
'click .js-select-list'() {
|
||||
const listId = this._id;
|
||||
|
|
@ -468,15 +313,10 @@ Template.miniList.events({
|
|||
},
|
||||
});
|
||||
|
||||
// Enable drag-reorder for collapsed lists from .js-collapsed-list-drag area
|
||||
this.$('.js-collapsed-list-drag').draggable({
|
||||
axis: 'x',
|
||||
helper: 'clone',
|
||||
revert: 'invalid',
|
||||
start(evt, ui) {
|
||||
boardComponent.setIsDragging(true);
|
||||
},
|
||||
stop(evt, ui) {
|
||||
boardComponent.setIsDragging(false);
|
||||
}
|
||||
});
|
||||
Template.miniList.helpers({
|
||||
isCurrentList() {
|
||||
const currentList = Utils.getCurrentList();
|
||||
const list = Template.currentData();
|
||||
return currentList && currentList._id == list._id;
|
||||
},
|
||||
});
|
||||
|
|
@ -4,17 +4,18 @@ template(name="listBody")
|
|||
.minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}")
|
||||
+inlinedForm(autoclose=false position="top")
|
||||
+addCardForm(listId=_id position="top")
|
||||
ul.sidebar-list
|
||||
each customFieldsSum
|
||||
li
|
||||
+viewer
|
||||
= name
|
||||
if $eq customFieldsSum.type "number"
|
||||
if customFieldSum.lenght
|
||||
ul.sidebar-list
|
||||
each customFieldsSum
|
||||
li
|
||||
+viewer
|
||||
= value
|
||||
if $eq customFieldsSum.type "currency"
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(value)
|
||||
= name
|
||||
if $eq customFieldsSum.type "number"
|
||||
+viewer
|
||||
= value
|
||||
if $eq customFieldsSum.type "currency"
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(value)
|
||||
each (cardsWithLimit (idOrNull ../../_id))
|
||||
a.minicard-wrapper.js-minicard(href=originRelativeUrl
|
||||
class="{{#if cardIsSelected}}is-selected{{/if}}"
|
||||
|
|
@ -25,15 +26,15 @@ template(name="listBody")
|
|||
+minicard(this)
|
||||
if (showSpinner (idOrNull ../../_id))
|
||||
+spinnerList
|
||||
|
||||
if canSeeAddCard
|
||||
+inlinedForm(autoclose=false position="bottom")
|
||||
+addCardForm(listId=_id position="bottom")
|
||||
else
|
||||
a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}")
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-card'}}
|
||||
+inlinedForm(autoclose=false position="bottom")
|
||||
+addCardForm(listId=_id position="bottom")
|
||||
a.minicard-wrapper.minicard-add-form
|
||||
+inlinedForm(autoclose=false position="bottom")
|
||||
+addCardForm(listId=_id position="bottom")
|
||||
else
|
||||
.add-card-wrapper
|
||||
a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="spinnerList")
|
||||
.sk-spinner.sk-spinner-list(
|
||||
|
|
@ -43,33 +44,30 @@ template(name="spinnerList")
|
|||
|
||||
template(name="addCardForm")
|
||||
.minicard.minicard-composer.js-composer
|
||||
if getLabels
|
||||
.minicard-labels
|
||||
each getLabels
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
textarea.minicard-composer-textarea.js-card-title(autofocus dir="auto")
|
||||
if members.get
|
||||
.minicard-members.js-minicard-composer-members
|
||||
each members.get
|
||||
+userAvatar(userId=this)
|
||||
.minicard-bottom
|
||||
.minicard-composer-icons
|
||||
if getLabels
|
||||
each getLabels
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
if members.get
|
||||
each members.get
|
||||
+userAvatar(userId=this)
|
||||
.add-controls.clearfix
|
||||
a.js-close-inlined-form
|
||||
i.fa.fa-times-thin
|
||||
|
||||
.add-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'add'}}
|
||||
a.js-close-inlined-form
|
||||
i.fa.fa-times-thin
|
||||
.add-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'add'}}
|
||||
|
||||
.links-controls.clearfix
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-link {{_ 'link'}}
|
||||
span.quiet
|
||||
|
|
||||
| /
|
||||
a.js-search {{_ 'search'}}
|
||||
span.quiet
|
||||
|
|
||||
| /
|
||||
a.js-card-template {{_ 'template'}}
|
||||
|
||||
template(name="autocompleteLabelLine")
|
||||
|
|
@ -77,70 +75,73 @@ template(name="autocompleteLabelLine")
|
|||
span(class="{{#if hasNoName}}quiet{{/if}}")= labelName
|
||||
|
||||
template(name="linkCardPopup")
|
||||
label {{_ 'boards'}}:
|
||||
.link-board-wrapper
|
||||
select.js-select-boards
|
||||
option(value="")
|
||||
each boards
|
||||
option(value="{{_id}}") {{isTitleDefault title}}
|
||||
input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}")
|
||||
|
||||
label {{_ 'swimlanes'}}:
|
||||
select.js-select-swimlanes
|
||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
||||
each swimlanes
|
||||
option(value="{{_id}}") {{isTitleDefault title}}
|
||||
|
||||
label {{_ 'lists'}}:
|
||||
select.js-select-lists
|
||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
||||
each lists
|
||||
option(value="{{_id}}") {{isTitleDefault title}}
|
||||
|
||||
label {{_ 'cards'}}:
|
||||
select.js-select-cards
|
||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
||||
each cards
|
||||
option(value="{{getRealId}}") {{getTitle}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
input.primary.confirm.js-done(type="button" value="{{_ 'link'}}")
|
||||
|
||||
template(name="searchElementPopup")
|
||||
form
|
||||
label
|
||||
| {{_ 'title'}}
|
||||
input.js-element-title(type="text" placeholder="{{_ 'title'}}" autofocus required dir="auto")
|
||||
unless isTemplateSearch
|
||||
label {{_ 'boards'}}:
|
||||
.link-board-wrapper
|
||||
.link-board-dropdown
|
||||
label {{_ 'boards'}}:
|
||||
select.js-select-boards
|
||||
option(value="")
|
||||
each boards
|
||||
option(value="{{_id}}") {{title}}
|
||||
form.js-search-term-form
|
||||
label
|
||||
| {{_ 'template'}}
|
||||
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
|
||||
.list-body.search-card-results
|
||||
.minicards.clearfix.js-minicards
|
||||
if isBoardTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+miniboard(this)
|
||||
if isListTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minilist(this)
|
||||
if isSwimlaneTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+miniswimlane(this)
|
||||
if isCardTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
unless isTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
option(value="{{_id}}") {{isTitleDefault title}}
|
||||
input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}")
|
||||
|
||||
.link-board-dropdown
|
||||
label {{_ 'swimlanes'}}:
|
||||
select.js-select-swimlanes
|
||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
||||
each swimlanes
|
||||
option(value="{{_id}}") {{isTitleDefault title}}
|
||||
.link-board-dropdown
|
||||
label {{_ 'lists'}}:
|
||||
select.js-select-lists
|
||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
||||
each lists
|
||||
option(value="{{_id}}") {{isTitleDefault title}}
|
||||
|
||||
.link-board-dropdown
|
||||
label {{_ 'cards'}}:
|
||||
select.js-select-cards
|
||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
||||
each cards
|
||||
option(value="{{getRealId}}") {{getTitle}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
input.primary.confirm.js-done(type="button" value="{{_ 'link'}}")
|
||||
|
||||
template(name="searchElementPopup")
|
||||
.link-board-wrapper
|
||||
form
|
||||
label
|
||||
| {{_ 'title'}}
|
||||
input.js-element-title(type="text" placeholder="{{_ 'title'}}" autofocus required dir="auto")
|
||||
unless isTemplateSearch
|
||||
label {{_ 'boards'}}:
|
||||
select.js-select-boards
|
||||
option(value="")
|
||||
each (boards)
|
||||
option(value="{{_id}}") {{title}}
|
||||
form.js-search-term-form
|
||||
label
|
||||
| {{_ 'template'}}
|
||||
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
|
||||
.list-body.search-card-results
|
||||
.minicards.clearfix.js-minicards
|
||||
if isBoardTemplateSearch
|
||||
each (results)
|
||||
a.minicard-wrapper.js-minicard
|
||||
+miniboard(this)
|
||||
if isListTemplateSearch
|
||||
each (results)
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minilist(this)
|
||||
if isSwimlaneTemplateSearch
|
||||
each (results)
|
||||
a.minicard-wrapper.js-minicard
|
||||
+miniswimlane(this)
|
||||
if isCardTemplateSearch
|
||||
each (results)
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
unless isTemplateSearch
|
||||
each (results)
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
|
|
|
|||
|
|
@ -3,16 +3,168 @@ import { TAPi18n } from '/imports/i18n';
|
|||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { Spinner } from '/client/lib/spinner';
|
||||
import getSlug from 'limax';
|
||||
import { itemsSelector } from './list';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
const InfiniteScrollIter = 10;
|
||||
|
||||
|
||||
function sortableCards(boardComponent, $cards) {
|
||||
return {
|
||||
connectWith: '.js-minicards:not(.js-list-full)',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper(evt, item) {
|
||||
const helper = item.clone();
|
||||
const cardHeight = item.height();
|
||||
const cardWidth = item.width();
|
||||
helper[0].setAttribute('style', `height: ${cardHeight}px !important; width: ${cardWidth}px !important;`);
|
||||
|
||||
if (MultiSelection.isActive()) {
|
||||
const andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
|
||||
if (andNOthers > 0) {
|
||||
helper.append(
|
||||
$(
|
||||
Blaze.toHTML(
|
||||
HTML.DIV(
|
||||
{ class: 'and-n-other' },
|
||||
TAPi18n.__('and-n-other-card', { count: andNOthers }),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return helper;
|
||||
},
|
||||
distance: 7,
|
||||
items: itemsSelector,
|
||||
placeholder: 'minicard-wrapper placeholder',
|
||||
/* cursor must be tied to smaller objects, position approximately from the button
|
||||
(can be computed if visually confusing) */
|
||||
cursorAt: { right: 20, top: 30 },
|
||||
start(evt, ui) {
|
||||
const cardHeight = ui.helper.height();
|
||||
ui.placeholder[0].setAttribute('style', `height: ${cardHeight}px !important;`);
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
boardComponent.setIsDragging(true);
|
||||
},
|
||||
stop(evt, ui) {
|
||||
// To attribute the new index number, we need to get the DOM element
|
||||
// of the previous and the following card -- if any.
|
||||
const prevCardDom = ui.item.prev('.js-minicard').get(0);
|
||||
const nextCardDom = ui.item.next('.js-minicard').get(0);
|
||||
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
|
||||
const sortIndex = Utils.calculateIndex(prevCardDom, nextCardDom, nCards);
|
||||
const listId = Blaze.getData(ui.item.parents('.list-body').get(0))._id;
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id;
|
||||
let targetSwimlaneId = null;
|
||||
|
||||
// only set a new swimelane ID if the swimlanes view is active
|
||||
if (
|
||||
Utils.boardView() === 'board-view-swimlanes' ||
|
||||
currentBoard.isTemplatesBoard()
|
||||
)
|
||||
targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))
|
||||
._id;
|
||||
|
||||
// Normally the jquery-ui sortable library moves the dragged DOM element
|
||||
// to its new position, which disrupts Blaze reactive updates mechanism
|
||||
// (especially when we move the last card of a list, or when multiple
|
||||
// users move some cards at the same time). To prevent these UX glitches
|
||||
// we ask sortable to gracefully cancel the move, and to put back the
|
||||
// DOM in its initial state. The card move is then handled reactively by
|
||||
// Blaze with the below query.
|
||||
$cards.sortable('cancel');
|
||||
|
||||
if (MultiSelection.isActive()) {
|
||||
ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] }).forEach((card, i) => {
|
||||
const newSwimlaneId = targetSwimlaneId
|
||||
? targetSwimlaneId
|
||||
: card.swimlaneId || defaultSwimlaneId;
|
||||
card.move(
|
||||
currentBoard._id,
|
||||
newSwimlaneId,
|
||||
listId,
|
||||
sortIndex.base + i * sortIndex.increment,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const cardDomElement = ui.item.get(0);
|
||||
const card = Blaze.getData(cardDomElement);
|
||||
const newSwimlaneId = targetSwimlaneId
|
||||
? targetSwimlaneId
|
||||
: card.swimlaneId || defaultSwimlaneId;
|
||||
card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base);
|
||||
}
|
||||
boardComponent.setIsDragging(false);
|
||||
},
|
||||
sort(event, ui) {
|
||||
Utils.scrollIfNeeded(event);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
// for infinite scrolling
|
||||
this.cardlimit = new ReactiveVar(InfiniteScrollIter);
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
// Prefer handling drag/sort in listBody rather than list as
|
||||
// it is shared between mobile and desktop view
|
||||
const boardComponent = BlazeComponent.getComponentForElement(document.getElementsByClassName('board-canvas')[0]);
|
||||
const $cards = this.$('.js-minicards');
|
||||
$cards.sortable(sortableCards(boardComponent, $cards));
|
||||
|
||||
this.autorun(() => {
|
||||
if ($cards.data('uiSortable') || $cards.data('sortable')) {
|
||||
// Use handle button on mobile, classic move otherwise
|
||||
if (Utils.isMiniScreen()) {
|
||||
$cards.sortable('option', 'handle', '.handle');
|
||||
} else {
|
||||
$cards.sortable('option', 'handle', '.minicard');
|
||||
}
|
||||
|
||||
$cards.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
// Disable drag-dropping when user is not member
|
||||
!Utils.canModifyBoard(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !Utils.canModifyBoard(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We want to re-run this function any time a card is added.
|
||||
this.autorun(() => {
|
||||
const currentBoardId = Tracker.nonreactive(() => {
|
||||
return Session.get('currentBoard');
|
||||
});
|
||||
Tracker.afterFlush(() => {
|
||||
$cards.find(itemsSelector).droppable({
|
||||
hoverClass: 'draggable-hover-card',
|
||||
accept: '.js-member,.js-label',
|
||||
drop(event, ui) {
|
||||
const cardId = Blaze.getData(this)._id;
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
|
||||
if (ui.draggable.hasClass('js-member')) {
|
||||
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
||||
card.assignMember(memberId);
|
||||
} else {
|
||||
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
||||
card.addLabel(labelId);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
mixins() {
|
||||
return [];
|
||||
},
|
||||
|
|
@ -82,9 +234,10 @@ BlazeComponent.extendComponent({
|
|||
evt.preventDefault();
|
||||
const firstCardDom = this.find('.js-minicard:first');
|
||||
const lastCardDom = this.find('.js-minicard:last');
|
||||
const textarea = $(evt.currentTarget).find('textarea');
|
||||
// more robust to start from the form
|
||||
const textarea = $(evt.currentTarget).closest('.inlined-form').find('textarea');
|
||||
const position = this.currentData().position;
|
||||
const title = textarea.val().trim();
|
||||
const title = $(textarea).val().trim();
|
||||
|
||||
let sortIndex;
|
||||
if (position === 'top') {
|
||||
|
|
@ -168,7 +321,6 @@ BlazeComponent.extendComponent({
|
|||
|
||||
// We keep the form opened, empty it, and scroll to it.
|
||||
textarea.val('').focus();
|
||||
autosize.update(textarea);
|
||||
if (position === 'bottom') {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
|
@ -194,21 +346,19 @@ BlazeComponent.extendComponent({
|
|||
|
||||
clickOnMiniCard(evt) {
|
||||
if (MultiSelection.isActive() || evt.shiftKey) {
|
||||
evt.stopImmediatePropagation();
|
||||
evt.preventDefault();
|
||||
const methodName = evt.shiftKey ? 'toggleRange' : 'toggle';
|
||||
MultiSelection[methodName](this.currentData()._id);
|
||||
|
||||
// If the card is already selected, we want to de-select it.
|
||||
// XXX We should probably modify the minicard href attribute instead of
|
||||
// overwriting the event in case the card is already selected.
|
||||
} else if (Utils.isMiniScreen()) {
|
||||
evt.preventDefault();
|
||||
Session.set('popupCardId', this.currentData()._id);
|
||||
this.cardDetailsPopup(evt);
|
||||
} else if (Session.equals('currentCard', this.currentData()._id)) {
|
||||
evt.stopImmediatePropagation();
|
||||
evt.preventDefault();
|
||||
// We need to wait a little because router gets called first,
|
||||
// we probably need a level of indirection
|
||||
// #FIXME remove if it works with commits we rebased on,
|
||||
// which change the route declaration order
|
||||
Meteor.setTimeout(() => {
|
||||
Session.set('currentCard', null)
|
||||
}, 50);
|
||||
Utils.goBoardId(Session.get('currentBoard'));
|
||||
} else {
|
||||
// Allow normal href navigation, but if it's the same card URL,
|
||||
|
|
@ -283,12 +433,6 @@ BlazeComponent.extendComponent({
|
|||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
cardDetailsPopup(event) {
|
||||
if (!Popup.isOpen()) {
|
||||
Popup.open("cardDetails")(event);
|
||||
}
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -296,6 +440,8 @@ BlazeComponent.extendComponent({
|
|||
'click .js-toggle-multi-selection': this.toggleMultiSelection,
|
||||
'click .open-minicard-composer': this.scrollToBottom,
|
||||
submit: this.addCard,
|
||||
// #FIXME remove in final MR if it works
|
||||
'click .confirm': this.addCard
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
@ -401,6 +547,17 @@ BlazeComponent.extendComponent({
|
|||
'click .js-link': Popup.open('linkCard'),
|
||||
'click .js-search': Popup.open('searchElement'),
|
||||
'click .js-card-template': Popup.open('searchElement'),
|
||||
submit: this.addCard,
|
||||
'click .minicard-label': (event) => {
|
||||
const clickedData = BlazeComponent.getComponentForElement(event.target).currentData?.()
|
||||
this.labels.set(this.labels.get().filter(e => e !== clickedData?._id));
|
||||
},
|
||||
'click .member': (event) => {
|
||||
const clickedData = BlazeComponent.getComponentForElement(event.target).currentData?.()
|
||||
this.members.set(this.members.get().filter(e => e !== clickedData?.userId));
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
@ -409,8 +566,6 @@ BlazeComponent.extendComponent({
|
|||
const editor = this;
|
||||
const $textarea = this.$('textarea');
|
||||
|
||||
autosize($textarea);
|
||||
|
||||
$textarea.escapeableTextComplete(
|
||||
[
|
||||
// User mentions
|
||||
|
|
@ -421,7 +576,9 @@ BlazeComponent.extendComponent({
|
|||
callback(
|
||||
$.map(currentBoard.activeMembers(), member => {
|
||||
const user = ReactiveCache.getUser(member.userId);
|
||||
return user.username.indexOf(term) === 0 ? user : null;
|
||||
return user.username.indexOf(term) === 0 &&
|
||||
// don't show already selected members
|
||||
!editor.members.get().find((e) => e === member.userId) ? user : null;
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
|
@ -445,8 +602,12 @@ BlazeComponent.extendComponent({
|
|||
const currentBoard = Utils.getCurrentBoard();
|
||||
callback(
|
||||
$.map(currentBoard.labels, label => {
|
||||
if (label.name == undefined) {
|
||||
label.name = "";
|
||||
if (
|
||||
label.name == undefined ||
|
||||
// don't show already selected labels
|
||||
editor.getLabels().find((e) => e._id === label._id)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
label.name.indexOf(term) > -1 ||
|
||||
|
|
@ -503,10 +664,10 @@ BlazeComponent.extendComponent({
|
|||
subManager.subscribe('board', this.boardId, false);
|
||||
this.board = ReactiveCache.getBoard(this.boardId);
|
||||
// List where to insert card
|
||||
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
this.list = $(PopupComponent.stack[0].openerElement).closest('.js-list');
|
||||
this.listId = Blaze.getData(this.list[0])._id;
|
||||
// Swimlane where to insert card
|
||||
const swimlane = $(Popup._getTopStack().openerElement).closest(
|
||||
const swimlane = $(PopupComponent.stack[0].openerElement).closest(
|
||||
'.js-swimlane',
|
||||
);
|
||||
this.swimlaneId = '';
|
||||
|
|
@ -539,10 +700,10 @@ BlazeComponent.extendComponent({
|
|||
if (!board) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// Ensure default swimlane exists
|
||||
board.getDefaultSwimline();
|
||||
|
||||
|
||||
const swimlanes = ReactiveCache.getSwimlanes(
|
||||
{
|
||||
boardId: this.selectedBoardId.get()
|
||||
|
|
@ -559,7 +720,8 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
const lists = ReactiveCache.getLists(
|
||||
{
|
||||
boardId: this.selectedBoardId.get()
|
||||
boardId: this.selectedBoardId.get(),
|
||||
swimlaneId: this.selectedSwimlaneId?.get?.()
|
||||
},
|
||||
{
|
||||
sort: { sort: 1 },
|
||||
|
|
@ -703,16 +865,16 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
onCreated() {
|
||||
this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
|
||||
this.isCardTemplateSearch = $(PopupComponent.stack[0].openerElement).hasClass(
|
||||
'js-card-template',
|
||||
);
|
||||
this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
|
||||
this.isListTemplateSearch = $(PopupComponent.stack[0].openerElement).hasClass(
|
||||
'js-list-template',
|
||||
);
|
||||
this.isSwimlaneTemplateSearch = $(
|
||||
Popup._getTopStack().openerElement,
|
||||
PopupComponent.stack[0].openerElement,
|
||||
).hasClass('js-open-add-swimlane-menu');
|
||||
this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
|
||||
this.isBoardTemplateSearch = $(PopupComponent.stack[0].openerElement).hasClass(
|
||||
'js-add-board',
|
||||
);
|
||||
this.isTemplateSearch =
|
||||
|
|
@ -731,20 +893,16 @@ BlazeComponent.extendComponent({
|
|||
} else {
|
||||
this.board = Utils.getCurrentBoard();
|
||||
}
|
||||
if (!this.board) {
|
||||
Popup.back();
|
||||
return;
|
||||
}
|
||||
this.boardId = this.board._id;
|
||||
this.boardId = this.board?._id;
|
||||
// Subscribe to this board
|
||||
subManager.subscribe('board', this.boardId, false);
|
||||
this.selectedBoardId = new ReactiveVar(this.boardId);
|
||||
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
|
||||
if (!this.isBoardTemplateSearch) {
|
||||
this.list = $(PopupComponent.stack[0].openerElement).closest('.js-list');
|
||||
this.swimlaneId = '';
|
||||
// Swimlane where to insert card
|
||||
const swimlane = $(Popup._getTopStack().openerElement).parents(
|
||||
const swimlane = $(PopupComponent.stack[0].openerElement).parents(
|
||||
'.js-swimlane',
|
||||
);
|
||||
if (Utils.boardView() === 'board-view-swimlanes')
|
||||
|
|
@ -783,11 +941,7 @@ BlazeComponent.extendComponent({
|
|||
} else if (this.isSwimlaneTemplateSearch) {
|
||||
return board.searchSwimlanes(this.term.get());
|
||||
} else if (this.isBoardTemplateSearch) {
|
||||
const boards = board.searchBoards(this.term.get());
|
||||
boards.forEach(board => {
|
||||
subManager.subscribe('board', board.linkedId, false);
|
||||
});
|
||||
return boards;
|
||||
return board.searchBoards(this.term.get());
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,66 +9,68 @@ template(name="listHeader")
|
|||
if currentList
|
||||
a.list-header-left-icon.js-unselect-list
|
||||
i.fa.fa-caret-left
|
||||
else
|
||||
if collapsed
|
||||
if showCardsCountForList cards.length
|
||||
br
|
||||
span.cardCount {{cardsCount}}
|
||||
if isMiniScreen
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
|/#{wipLimit.value})
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
if hasNumberFieldsSum
|
||||
|
|
||||
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
|
||||
else
|
||||
a.list-collapse-indicator.js-collapse(title="{{_ 'collapse'}}")
|
||||
if collapsed
|
||||
else
|
||||
//- start by this on mobile to have cohesion with other views
|
||||
a.list-header-menu-icon.js-select-list
|
||||
i.fa.fa-caret-right
|
||||
else
|
||||
i.fa.fa-caret-down
|
||||
div(class="{{#if collapsed}}list-rotated{{/if}}")
|
||||
.list-header-name-container
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#unless collapsed}}{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}{{/unless}}")
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
|/#{wipLimit.value})
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
|/#{wipLimit.value})
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
if hasNumberFieldsSum
|
||||
|
|
||||
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
|
||||
else
|
||||
div.list-header-name-container
|
||||
unless isMiniScreen
|
||||
a.list-collapse-indicator.js-collapse(title="{{_ 'collapse'}}")
|
||||
if collapsed
|
||||
i.fa.fa-caret-right
|
||||
else
|
||||
i.fa.fa-caret-down
|
||||
div(class="{{#if collapsed}}list-rotated{{/if}}").list-header-wrap
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
|/#{wipLimit.value})
|
||||
unless collapsed
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
if hasNumberFieldsSum
|
||||
|
|
||||
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
if isMiniScreen
|
||||
if currentList
|
||||
if isWatching
|
||||
i.list-header-watch-icon i.fa.fa-eye
|
||||
i.list-header-watch-icon.i.fa.fa-eye
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
if canSeeAddCard
|
||||
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
|
||||
i.fa.fa-plus
|
||||
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
else
|
||||
a.list-header-menu-icon.js-select-list
|
||||
i.fa.fa-caret-right
|
||||
unless currentUser.isWorker
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
if isMiniScreen
|
||||
a.list-header-handle.handle.js-list-handle
|
||||
i.fa.fa-arrows
|
||||
else if currentUser.isBoardMember
|
||||
|
|
@ -77,24 +79,13 @@ template(name="listHeader")
|
|||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
if isMiniScreen
|
||||
a.list-header-handle-desktop.handle.js-list-handle(title="{{_ 'drag-list'}}")
|
||||
i.fa.fa-arrows
|
||||
unless collapsed
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
//if isBoardAdmin
|
||||
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.list-header-handle-desktop.handle.js-list-handle(title="{{_ 'drag-list'}}")
|
||||
i.fa.fa-arrows
|
||||
if canSeeAddCard
|
||||
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
|
||||
i.fa.fa-plus
|
||||
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
unless isMiniScreen
|
||||
if collapsed
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}}
|
||||
|
||||
template(name="editListTitleForm")
|
||||
.list-composer
|
||||
|
|
@ -224,14 +215,14 @@ template(name="wipLimitErrorPopup")
|
|||
.wip-limit-invalid
|
||||
p {{_ 'wipLimitErrorPopup-dialog-pt1'}}
|
||||
p {{_ 'wipLimitErrorPopup-dialog-pt2'}}
|
||||
button.full.js-back-view(type="submit") {{_ 'cancel'}}
|
||||
button.negate.js-back-view(type="submit") {{_ 'cancel'}}
|
||||
|
||||
template(name="setListWidthPopup")
|
||||
#js-list-width-edit
|
||||
label {{_ 'set-list-width-value'}}
|
||||
p
|
||||
input.list-width-value(type="number" value="{{ listWidthValue }}" min="270")
|
||||
input.list-constraint-value(type="number" value="{{ listConstraintValue }}" min="270")
|
||||
input.list-width-value(type="number" value="{{ listWidthValue }}" min="100")
|
||||
input.list-constraint-value(type="number" value="{{ listConstraintValue }}" min="100")
|
||||
input.list-width-apply(type="submit" value="{{_ 'apply'}}")
|
||||
input.list-width-error
|
||||
br
|
||||
|
|
@ -242,8 +233,8 @@ template(name="setListWidthPopup")
|
|||
|
||||
template(name="listWidthErrorPopup")
|
||||
.list-width-invalid
|
||||
p {{_ 'list-width-error-message'}} '>=270'
|
||||
button.full.js-back-view(type="submit") {{_ 'cancel'}}
|
||||
p {{_ 'list-width-error-message'}} '>=100'
|
||||
button.negate.js-back-view(type="submit") {{_ 'cancel'}}
|
||||
|
||||
template(name="setListColorPopup")
|
||||
form.edit-label
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@ Meteor.startup(() => {
|
|||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onRendered() {
|
||||
/* #FIXME I have no idea why this exact same
|
||||
event won't fire when in event maps */
|
||||
$(this.find('.js-collapse')).on('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.collapsed(!this.collapsed());
|
||||
});
|
||||
},
|
||||
|
||||
canSeeAddCard() {
|
||||
const list = Template.currentData();
|
||||
return (
|
||||
|
|
@ -34,7 +43,7 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
},
|
||||
collapsed(check = undefined) {
|
||||
const list = Template.currentData();
|
||||
const list = this.data();
|
||||
const status = Utils.getListCollapseState(list);
|
||||
if (check === undefined) {
|
||||
// just check
|
||||
|
|
@ -110,7 +119,11 @@ BlazeComponent.extendComponent({
|
|||
return TAPi18n.__('cards-count');
|
||||
}
|
||||
},
|
||||
|
||||
currentList() {
|
||||
const currentList = Utils.getCurrentList();
|
||||
const list = Template.currentData();
|
||||
return currentList && currentList._id == list._id;
|
||||
},
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -118,10 +131,6 @@ BlazeComponent.extendComponent({
|
|||
event.preventDefault();
|
||||
this.starred(!this.starred());
|
||||
},
|
||||
'click .js-collapse'(event) {
|
||||
event.preventDefault();
|
||||
this.collapsed(!this.collapsed());
|
||||
},
|
||||
'click .js-open-list-menu': Popup.open('listAction'),
|
||||
'click .js-add-card.list-header-plus-top'(event) {
|
||||
const listDom = $(event.target).parents(
|
||||
|
|
@ -459,10 +468,10 @@ BlazeComponent.extendComponent({
|
|||
this.currentBoard = Utils.getCurrentBoard();
|
||||
this.currentSwimlaneId = new ReactiveVar(null);
|
||||
this.currentListId = new ReactiveVar(null);
|
||||
|
||||
|
||||
// Get the swimlane context from opener
|
||||
const openerData = Popup.getOpenerComponent()?.data();
|
||||
|
||||
|
||||
// If opened from swimlane menu, openerData is the swimlane
|
||||
if (openerData?.type === 'swimlane' || openerData?.type === 'template-swimlane') {
|
||||
this.currentSwimlane = openerData;
|
||||
|
|
@ -554,4 +563,3 @@ BlazeComponent.extendComponent({
|
|||
];
|
||||
},
|
||||
}).register('addListPopup');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.my-cards-board-wrapper {
|
||||
border-radius: 0 0 0.5vw 0.5vw;
|
||||
min-width: min(400px, 52vw);
|
||||
min-width: min(100%, 400px, 52vw);
|
||||
margin-bottom: 2.5vh;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
|
@ -33,13 +33,6 @@
|
|||
text-align: center;
|
||||
margin-bottom: 0.9vh;
|
||||
}
|
||||
.my-cards-list-wrapper {
|
||||
margin: 1.3vh 1.3vw;
|
||||
border-radius: 0.7vw;
|
||||
display: inline-grid;
|
||||
min-width: min(250px, 32vw);
|
||||
max-width: min(350px, 45vw);
|
||||
}
|
||||
.my-cards-card-wrapper {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.3vh;
|
||||
|
|
@ -81,7 +74,7 @@
|
|||
}
|
||||
|
||||
.accessibility-page h2 {
|
||||
font-size: 24px;
|
||||
|
||||
margin-bottom: 20px;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
.new-comment a.fa.fa-brands.fa-markdown,
|
||||
.inlined-form a.fa.fa-brands.fa-markdown {
|
||||
float: right;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: 60px;
|
||||
.new-comment, .inlined-form {
|
||||
a.fa.fa-brands.fa-markdown, a.fa.fa-copy {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
.new-comment a.fa.fa-copy,
|
||||
.inlined-form a.fa.fa-copy {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
right: 5px;
|
||||
}
|
||||
.js-inlined-form.viewer.btn-sm {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 6px;
|
||||
.editor-controls {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
grid-area: editor-controls;
|
||||
align-items: center;
|
||||
align-self: start;
|
||||
gap: 1ch;
|
||||
}
|
||||
|
||||
.editor {
|
||||
grid-area: editor;
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
template(name="editor")
|
||||
a.fa.fa-brands.fa-markdown(title="{{_ 'convert-to-markdown'}}")
|
||||
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
|
||||
span.copied-tooltip {{_ 'copied'}}
|
||||
.editor-controls
|
||||
a.fa.fa-brands.fa-markdown(title="{{_ 'convert-to-markdown'}}")
|
||||
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
|
||||
span.copied-tooltip.copied-tooltip-hidden {{_ 'copied'}}
|
||||
textarea.editor(
|
||||
dir="auto"
|
||||
class="{{class}}"
|
||||
id=id
|
||||
autofocus=autofocus
|
||||
placeholder="{{_ 'comment-placeholder'}}")
|
||||
+Template.contentBlock
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ BlazeComponent.extendComponent({
|
|||
|
||||
const enableTextarea = function() {
|
||||
const $textarea = this.$(textareaSelector);
|
||||
autosize($textarea);
|
||||
$textarea.escapeableTextComplete(mentions);
|
||||
};
|
||||
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === true || Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === 'true') {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.global-search-board-wrapper {
|
||||
border-radius: 8px;
|
||||
min-width: 400px;
|
||||
border-radius: 0.8ch;
|
||||
min-width: min(100%, 400px);
|
||||
border-width: 8px;
|
||||
border-color: #808080;
|
||||
border-style: solid;
|
||||
|
|
@ -67,8 +67,6 @@
|
|||
color: #8b0000;
|
||||
}
|
||||
.global-search-page {
|
||||
width: 40%;
|
||||
min-width: 400px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
line-height: 150%;
|
||||
|
|
@ -91,6 +89,13 @@
|
|||
font-family: Courier;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.lists-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1ch 0.3lh;
|
||||
|
||||
}
|
||||
code {
|
||||
color: #000;
|
||||
background-color: #d3d3d3;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,100 +5,81 @@ template(name="header")
|
|||
Reddit "subreddit" bar.
|
||||
The first link goes to the boards page.
|
||||
if currentUser
|
||||
#header-quick-access(class=currentBoard.colorClass)
|
||||
#header-quick-access(class="currentBoard.colorClass {{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
// Home icon - always at left side of logo
|
||||
span.home-icon.allBoards
|
||||
a(href="{{pathFor 'home'}}")
|
||||
i.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
#header-quick-access-left
|
||||
span.home-icon.allBoards
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-home
|
||||
span
|
||||
| {{_ 'all-boards'}}
|
||||
|
||||
// Logo - visible; on mobile constrained by CSS
|
||||
unless currentSetting.hideLogo
|
||||
if currentSetting.customTopLeftCornerLogoImageUrl
|
||||
if currentSetting.customTopLeftCornerLogoLinkUrl
|
||||
a(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0")
|
||||
unless currentSetting.customTopLeftCornerLogoLinkUrl
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
unless currentSetting.customTopLeftCornerLogoImageUrl
|
||||
div#headerIsSettingDatabaseCallDone
|
||||
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
|
||||
// Zoom controls - always visible
|
||||
.zoom-controls
|
||||
span.zoom-level.js-zoom-level-click(title="{{_ 'click-to-change-zoom'}}")
|
||||
span.zoom-display {{zoomLevel}}%
|
||||
input.zoom-input.js-zoom-input(type="number" value=zoomLevel min="50" max="300" step="10" style="display: none;")
|
||||
|
||||
// Drag handles toggle - between zoom and mobile mode toggle
|
||||
a.board-header-btn.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}")
|
||||
i.fa.fa-arrows
|
||||
if isShowDesktopDragHandles
|
||||
i.fa.fa-check
|
||||
unless isShowDesktopDragHandles
|
||||
i.fa.fa-ban
|
||||
|
||||
if isMiniScreen
|
||||
ul.header-quick-access-list
|
||||
if currentList
|
||||
each currentBoard.lists
|
||||
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
|
||||
a.js-select-list
|
||||
+viewer
|
||||
= title
|
||||
else
|
||||
if isMiniScreen
|
||||
ul.header-quick-access-list
|
||||
if currentList
|
||||
each currentBoard.lists
|
||||
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
|
||||
a.js-select-list.
|
||||
+viewer
|
||||
= title
|
||||
else
|
||||
each currentUser.starredBoards
|
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
+viewer
|
||||
= title
|
||||
else
|
||||
ul.header-quick-access-list
|
||||
//li
|
||||
// a(href="{{pathFor 'public'}}")
|
||||
// span.fa.fa-globe
|
||||
// | {{_ 'public'}}
|
||||
each currentUser.starredBoards
|
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
+viewer
|
||||
= title
|
||||
else
|
||||
li.current.empty(title="{{_ 'quick-access-description'}}")
|
||||
| {{_ 'quick-access-description'}}
|
||||
#header-new-board-icon
|
||||
// Next line is used only for spacing at header,
|
||||
// there is no visible clickable icon.
|
||||
#header-new-board-icon
|
||||
else
|
||||
ul.header-quick-access-list
|
||||
//li
|
||||
// a(href="{{pathFor 'public'}}")
|
||||
// span.fa.fa-globe
|
||||
// | {{_ 'public'}}
|
||||
each currentUser.starredBoards
|
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
+viewer
|
||||
= title
|
||||
// Hide duplicate create board button,
|
||||
// because it did not show board templates correctly.
|
||||
//a#header-new-board-icon.js-create-board
|
||||
// i.fa.fa-plus(title="Create a new board")
|
||||
// Logo - visible; on mobile constrained by CSS
|
||||
unless currentSetting.hideLogo
|
||||
.logo-container
|
||||
if currentSetting.customTopLeftCornerLogoImageUrl
|
||||
if currentSetting.customTopLeftCornerLogoLinkUrl
|
||||
a.logo(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
+logo
|
||||
else
|
||||
+logo
|
||||
else
|
||||
li.current.empty(title="{{_ 'quick-access-description'}}")
|
||||
| {{_ 'quick-access-description'}}
|
||||
#header-new-board-icon
|
||||
// Next line is used only for spacing at header,
|
||||
// there is no visible clickable icon.
|
||||
#header-new-board-icon
|
||||
// Hide duplicate create board button,
|
||||
// because it did not show board templates correctly.
|
||||
//a#header-new-board-icon.js-create-board
|
||||
// i.fa.fa-plus(title="Create a new board")
|
||||
div#headerIsSettingDatabaseCallDone.logo
|
||||
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
|
||||
.mobile-mode-toggle
|
||||
a.board-header-btn.js-mobile-mode-toggle(title="{{_ 'mobile-desktop-toggle'}}" class="{{#if mobileMode}}mobile-active{{else}}desktop-active{{/if}}")
|
||||
i.mobile-icon(class="{{#if mobileMode}}active{{/if}}")
|
||||
i.fa.fa-mobile
|
||||
i.desktop-icon(class="{{#unless mobileMode}}active{{/unless}}")
|
||||
i.fa.fa-desktop
|
||||
|
||||
// Notifications
|
||||
+notifications
|
||||
|
||||
if currentSetting.customHelpLinkUrl
|
||||
#header-help
|
||||
a(href="{{currentSetting.customHelpLinkUrl}}", title="{{_ 'help'}}", target="_blank", rel="noopener noreferrer")
|
||||
i.fa.fa-question-circle
|
||||
|
||||
+headerUserBar
|
||||
#header-quick-access-right
|
||||
if currentSetting.customHelpLinkUrl
|
||||
#header-help
|
||||
a(href="{{currentSetting.customHelpLinkUrl}}", title="{{_ 'help'}}", target="_blank", rel="noopener noreferrer")
|
||||
i.fa.fa-question-circle
|
||||
#header-quick-access-icons
|
||||
+headerUserBar
|
||||
// Notifications
|
||||
+notifications
|
||||
|
||||
#header(class=currentBoard.colorClass)
|
||||
//-
|
||||
The main bar is a colorful bar that provide all the meta-data for the
|
||||
current page. This bar is contextual based.
|
||||
If the user is not connected we display "sign in" and "log in" buttons.
|
||||
#header-main-bar(class="{{#if wrappedHeader}}wrapper{{/if}}")
|
||||
#header-main-bar(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if wrappedHeader}}wrapper{{/if}}")
|
||||
+Template.dynamic(template=headerBar)
|
||||
|
||||
if appIsOffline
|
||||
|
|
@ -122,3 +103,7 @@ template(name="offlineWarning")
|
|||
| {{_ 'app-is-offline'}}
|
||||
|
||||
a.app-try-reconnect {{_ 'app-try-reconnect'}}
|
||||
|
||||
//- a little helper to avoid duplication
|
||||
template(name="logo")
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" style="{{#if currentSetting.customTopLeftCornerLogoHeight}}min-height: #{currentSetting.customTopLeftCornerLogoHeight};{{/if}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
|
|
@ -22,13 +22,13 @@ Template.header.onCreated(function () {
|
|||
)
|
||||
document.getElementById(
|
||||
'headerIsSettingDatabaseCallDone',
|
||||
).style.display = 'none';
|
||||
).style.visibility = 'hidden';
|
||||
else if (
|
||||
document.getElementById('headerIsSettingDatabaseCallDone') != null
|
||||
)
|
||||
document.getElementById(
|
||||
'headerIsSettingDatabaseCallDone',
|
||||
).style.display = 'block';
|
||||
).style.visibility = 'visible';
|
||||
return this.stop();
|
||||
},
|
||||
});
|
||||
|
|
@ -57,14 +57,6 @@ Template.header.helpers({
|
|||
return announcements && announcements.body;
|
||||
},
|
||||
|
||||
zoomLevel() {
|
||||
const sessionZoom = Session.get('wekan-zoom-level');
|
||||
if (sessionZoom !== undefined) {
|
||||
return Math.round(sessionZoom * 100);
|
||||
}
|
||||
return Math.round(Utils.getZoomLevel() * 100);
|
||||
},
|
||||
|
||||
mobileMode() {
|
||||
const sessionMode = Session.get('wekan-mobile-mode');
|
||||
if (sessionMode !== undefined) {
|
||||
|
|
@ -76,51 +68,6 @@ Template.header.helpers({
|
|||
|
||||
Template.header.events({
|
||||
'click .js-create-board': Popup.open('headerBarCreateBoard'),
|
||||
'click .js-zoom-level-click'(evt) {
|
||||
const $zoomDisplay = $(evt.currentTarget).find('.zoom-display');
|
||||
const $zoomInput = $(evt.currentTarget).find('.zoom-input');
|
||||
|
||||
// Hide display, show input
|
||||
$zoomDisplay.hide();
|
||||
$zoomInput.show().focus().select();
|
||||
},
|
||||
|
||||
'keypress .js-zoom-input'(evt) {
|
||||
if (evt.which === 13) {
|
||||
// Enter key
|
||||
const newZoomPercent = parseInt(evt.target.value);
|
||||
|
||||
if (
|
||||
!isNaN(newZoomPercent) &&
|
||||
newZoomPercent >= 50 &&
|
||||
newZoomPercent <= 300
|
||||
) {
|
||||
const newZoom = newZoomPercent / 100;
|
||||
Utils.setZoomLevel(newZoom);
|
||||
|
||||
// Hide input, show display
|
||||
const $zoomDisplay = $(evt.target).siblings('.zoom-display');
|
||||
const $zoomInput = $(evt.target);
|
||||
$zoomInput.hide();
|
||||
$zoomDisplay.show();
|
||||
} else {
|
||||
alert('Please enter a zoom level between 50% and 300%');
|
||||
evt.target.focus().select();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'blur .js-zoom-input'(evt) {
|
||||
// When input loses focus, hide it and show display
|
||||
const $zoomDisplay = $(evt.target).siblings('.zoom-display');
|
||||
const $zoomInput = $(evt.target);
|
||||
$zoomInput.hide();
|
||||
$zoomDisplay.show();
|
||||
},
|
||||
'click .js-mobile-mode-toggle'() {
|
||||
const currentMode = Utils.getMobileMode();
|
||||
Utils.setMobileMode(!currentMode);
|
||||
},
|
||||
'click .js-open-bookmarks'(evt) {
|
||||
// Already added but ensure single definition -- safe guard
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
.shortcuts-list .shortcuts-list-item .shortcuts-list-item-keys kbd {
|
||||
padding: 5px 8px;
|
||||
margin: 5px;
|
||||
font-size: 18px;
|
||||
|
||||
}
|
||||
.shortcuts-list .shortcuts-list-item .shortcuts-list-item-action {
|
||||
font-size: 1.4em;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,33 @@
|
|||
* {
|
||||
-webkit-box-sizing: unset;
|
||||
box-sizing: unset;
|
||||
/* Global variables that we can use to easily test and change layout
|
||||
Later it could be useful to use a CSS superset */
|
||||
/* this makes the property computable */
|
||||
@property --popup-margin {
|
||||
syntax: "<length>";
|
||||
inherits: true;
|
||||
initial-value: 0px;
|
||||
}
|
||||
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
--label-height: 1.7lh;
|
||||
--header-scale: clamp(1rem, 1.333rem + -0.333vw, 1.3rem)
|
||||
--popup-margin: 2vmax;
|
||||
|
||||
/* regarding fonts, this is one of the clearest I found: https://modern-fluid-typography.vercel.app/ */
|
||||
&:has(body.desktop-mode) {
|
||||
font-size: clamp(1rem, 1.68rem + -0.57vw, 1.4rem);
|
||||
--quick-header-scale: clamp(0.8rem, 0.6rem + 0.4vw, 1.2rem);
|
||||
--list-item-size: 1.2em;
|
||||
}
|
||||
|
||||
&:has(body.mobile-mode) {
|
||||
font-size: clamp(2.5rem, 3vw + 1.7rem, 3.5rem);
|
||||
--quick-header-scale: 1.3em;
|
||||
--header-scale: clamp(1rem, -0.5vw + 1.25rem, 1.125rem);
|
||||
--list-item-size: 1.6em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fixed missing 'import nib' stylesheet reset and extra li bullet points
|
||||
* https://github.com/wekan/wekan/issues/4512#issuecomment-1129347536
|
||||
*/
|
||||
|
|
@ -32,29 +58,26 @@ a:focus {
|
|||
color: unset;
|
||||
text-decoration: unset;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: unset;
|
||||
min-width: unset;
|
||||
padding: unset;
|
||||
font-size: unset;
|
||||
font-weight: unset;
|
||||
line-height: unset;
|
||||
color: unset;
|
||||
text-align: unset;
|
||||
white-space: unset;
|
||||
vertical-align: unset;
|
||||
background-color: unset;
|
||||
border-radius: unset;
|
||||
display: flex;
|
||||
gap: 0 0.3ch;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
body {
|
||||
/* changed programmatically on swimlane resizes, or e.g. when un-collapsed */
|
||||
transition: height 0.2s ease-out, width 0.2s ease-out;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
button {
|
||||
font: clamp(14px, 2.5vw, 18px) Roboto, Poppins, "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
line-height: 1.4;
|
||||
color: #4d4d4d;
|
||||
font-family: Roboto, Poppins, "Helvetica Neue", "Liberation Sans", Arial, Helvetica, sans-serif;
|
||||
color: hsl(0, 0%, 30%);
|
||||
/* Improve text rendering */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
|
@ -63,58 +86,74 @@ button {
|
|||
user-select: text;
|
||||
}
|
||||
html {
|
||||
font-size: 100%;
|
||||
max-height: 100%;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
overscroll-behavior: none;
|
||||
|
||||
}
|
||||
body {
|
||||
background: #dedede;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
/* iOS Safari fixes */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
align-items: stretch;
|
||||
justify-content: start;
|
||||
/* height is auto; if set to 100vh, it prevents navbar to disappear on scroll... */
|
||||
width: 100%;
|
||||
/* Needs to be set on body and html. Feels ok to disable entirely as Wekan is really drag/scroll-heavy */
|
||||
overscroll-behavior: none;
|
||||
min-height: 100vh;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Mobile mode specific fixes for iOS Safari */
|
||||
body.mobile-mode {
|
||||
overflow-x: hidden;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
/* Prevent iOS Safari bounce scroll */
|
||||
overscroll-behavior: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Ensure content area is scrollable in mobile mode */
|
||||
body.mobile-mode #content {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
height: calc(100vh - 48px);
|
||||
}
|
||||
|
||||
/* Prevent scroll through popups */
|
||||
body:has(.pop-over:hover) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Some forms will need extra adjustement (removing margins, etc)
|
||||
but it worth it to let browsers take care of exact placement/sizing */
|
||||
.inlined-form {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
gap: 0.3lh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 1vh;
|
||||
min-height: 100vh;
|
||||
max-width: min(100%, 100vw);
|
||||
}
|
||||
#content .sk-spinner {
|
||||
margin-top: 30vh;
|
||||
}
|
||||
#content > .wrapper {
|
||||
margin-top: 1vh;
|
||||
padding: 2vh 2vw;
|
||||
}
|
||||
#modal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
@ -157,25 +196,6 @@ body.mobile-mode #content {
|
|||
#modal .modal-content-wide .modal-close-btn {
|
||||
display: block;
|
||||
float: right;
|
||||
font-size: clamp(18px, 4vw, 24px);
|
||||
}
|
||||
h1 {
|
||||
font-size: clamp(18px, 4vw, 24px);
|
||||
line-height: 1.2em;
|
||||
margin: 0 0 1vh;
|
||||
}
|
||||
h2 {
|
||||
font-size: clamp(16px, 3.5vw, 20px);
|
||||
line-height: 1.2em;
|
||||
margin: 0 0 0.8vh;
|
||||
}
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: clamp(14px, 3vw, 18px);
|
||||
line-height: 1.25em;
|
||||
margin: 0 0 0.6vh;
|
||||
}
|
||||
.quiet,
|
||||
.quiet a {
|
||||
|
|
@ -226,7 +246,7 @@ p {
|
|||
}
|
||||
p a {
|
||||
text-decoration: underline;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
table,
|
||||
p {
|
||||
|
|
@ -250,13 +270,13 @@ blockquote {
|
|||
padding: 0 0 0 1vw;
|
||||
}
|
||||
hr {
|
||||
height: 1px;
|
||||
height: 0.2ch;
|
||||
border: 0;
|
||||
border: none;
|
||||
width: 100%;
|
||||
background: #dbdbdb;
|
||||
color: #dbdbdb;
|
||||
margin: 2vh 0;
|
||||
margin: 0.2lh 0;
|
||||
padding: 0;
|
||||
}
|
||||
table,
|
||||
|
|
@ -303,7 +323,7 @@ kbd {
|
|||
clear: both;
|
||||
}
|
||||
.hide {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
.show {
|
||||
display: block;
|
||||
|
|
@ -337,8 +357,11 @@ kbd {
|
|||
padding-bottom: 0;
|
||||
}
|
||||
.wrapper {
|
||||
width: calc(100% - 2vw);
|
||||
margin: 0 auto;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
width: auto;
|
||||
height: fit-content;
|
||||
display: grid;
|
||||
}
|
||||
.relative {
|
||||
position: relative;
|
||||
|
|
@ -369,8 +392,12 @@ kbd {
|
|||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
.invisible-line {
|
||||
height: 1.3lh;
|
||||
visibility: hidden;
|
||||
}
|
||||
.wrapword {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.grab {
|
||||
cursor: grab;
|
||||
|
|
@ -445,8 +472,39 @@ a:not(.disabled).is-active i.fa {
|
|||
}
|
||||
.viewer {
|
||||
min-height: 2.5vh;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
/* a tentative to get layout less dependant of content,
|
||||
especially for small elements e.g. labels: the goal is that
|
||||
content will be cut with `...` if too large (but will be fully
|
||||
rendered in dedicated interfaces)
|
||||
|
||||
the classic technique is to use flex-basis, but it depends
|
||||
on the parent not overflowing to get the right size; also,
|
||||
specifying in terms of lines makes the browser act clever, by
|
||||
fitting the available space and cutting after N lines, whatever
|
||||
is the text's length */
|
||||
min-width: 0;
|
||||
p, ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
/* See https: //css-tricks.com/line-clampin/,
|
||||
it is widely supported and waiting standardization https: //caniuse.com/?search=-webkit-line-clamp */
|
||||
display: -webkit-box !important;
|
||||
/* 0 has no effect; ensures will not interfere unless asked */
|
||||
-webkit-line-clamp: var(--overflow-lines, 0);
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-align-items: center;
|
||||
/* grid properties apply */
|
||||
align-content: center;
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
.viewer table {
|
||||
word-wrap: normal;
|
||||
|
|
@ -481,6 +539,12 @@ a:not(.disabled).is-active i.fa {
|
|||
padding: 0;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.basicTabs-container .tabs-list .tab-item {
|
||||
/* where does templates_tabs.css come from? visible in
|
||||
devtools but not in sources */
|
||||
font-size: unset !important;
|
||||
}
|
||||
.no-scrollbars {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
|
@ -495,133 +559,30 @@ a:not(.disabled).is-active i.fa {
|
|||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape),
|
||||
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait) {
|
||||
#content {
|
||||
margin: 1px 0px 0px 0px;
|
||||
height: calc(100% - 0px);
|
||||
/* Improve touch scrolling */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
#content > .wrapper {
|
||||
margin-top: 0px;
|
||||
padding: 8px;
|
||||
}
|
||||
.wrapper {
|
||||
height: calc(100% - 31px);
|
||||
margin: 0px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.panel-default {
|
||||
width: 95vw;
|
||||
max-width: 95vw;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
/* Improve touch targets */
|
||||
button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px; /* Prevent zoom on iOS */
|
||||
/* Prevent zoom on iOS */
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
|
||||
/* Form elements */
|
||||
input, select, textarea {
|
||||
font-size: 16px; /* Prevent zoom on iOS */
|
||||
/* Prevent zoom on iOS */
|
||||
padding: 12px;
|
||||
min-height: 44px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
/* Cards and lists */
|
||||
.minicard {
|
||||
min-height: 48px;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 8px;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
/* Board canvas */
|
||||
.board-canvas {
|
||||
padding: 0 8px 8px 0;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Header mobile layout */
|
||||
#header {
|
||||
padding: 8px;
|
||||
/* Keep top bar on a single row on small screens */
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#header-quick-access {
|
||||
/* Keep quick-access items in one row */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Hide elements that should move to the hamburger menu on mobile */
|
||||
#header-quick-access .header-quick-access-list,
|
||||
#header-quick-access #header-help {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only the home icon (hide the trailing text) on mobile */
|
||||
#header-quick-access .home-icon a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 28px; /* enough to display the icon */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Hide text in home icon on mobile, show only icon */
|
||||
#header-quick-access .home-icon a span:not(.fa) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Ensure proper spacing for mobile header elements */
|
||||
#header-quick-access .zoom-controls {
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mobile-mode-toggle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#header-user-bar {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Ensure header elements don't wrap on very small screens */
|
||||
#header-quick-access {
|
||||
min-width: 0; /* Allow flexbox to shrink */
|
||||
}
|
||||
|
||||
/* Make sure logo doesn't take too much space on mobile */
|
||||
#header-quick-access img {
|
||||
max-height: 24px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
/* Ensure zoom controls are compact on mobile */
|
||||
.zoom-controls .zoom-level {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Modal mobile optimization */
|
||||
#modal .modal-content,
|
||||
#modal .modal-content-wide {
|
||||
|
|
@ -632,29 +593,28 @@ a:not(.disabled).is-active i.fa {
|
|||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
/* Table mobile optimization */
|
||||
table {
|
||||
font-size: 14px;
|
||||
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
|
||||
/* Admin panel mobile optimization */
|
||||
.setting-content .content-body {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 100%;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
|
||||
.setting-content .content-body .main-body {
|
||||
order: 1;
|
||||
min-height: 60vh;
|
||||
|
|
@ -663,139 +623,175 @@ a:not(.disabled).is-active i.fa {
|
|||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
/* Tablet devices (768px - 1024px) */
|
||||
@media screen and (min-width: 768px) and (max-width: 1024px) {
|
||||
#content > .wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
|
||||
.wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
|
||||
.panel-default {
|
||||
width: 90vw;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
|
||||
/* Touch-friendly but more compact */
|
||||
button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
|
||||
min-height: 48px;
|
||||
min-width: 48px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
|
||||
.minicard {
|
||||
min-height: 40px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
.list {
|
||||
margin: 0 12px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
|
||||
.board-canvas {
|
||||
padding: 0 12px 12px 0;
|
||||
}
|
||||
|
||||
|
||||
#header {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
|
||||
#modal .modal-content {
|
||||
width: 80vw;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
|
||||
#modal .modal-content-wide {
|
||||
width: 90vw;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
|
||||
.setting-content .content-body {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive handling for quick-access description on tablets */
|
||||
#header-quick-access ul.header-quick-access-list li.current.empty {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
||||||| parent of 2e0149f79 (🚧 Remove zoom/mobile option, rework header/misc layout to be more responsive)
|
||||
/* Tablet devices (768px - 1024px) */
|
||||
@media screen and (min-width: 768px) and (max-width: 1024px) {
|
||||
#content > .wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.panel-default {
|
||||
width: 90vw;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
/* Touch-friendly but more compact */
|
||||
button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
|
||||
min-height: 48px;
|
||||
min-width: 48px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.minicard {
|
||||
min-height: 40px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 12px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.board-canvas {
|
||||
padding: 0 12px 12px 0;
|
||||
}
|
||||
|
||||
#header {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
#modal .modal-content {
|
||||
width: 80vw;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
#modal .modal-content-wide {
|
||||
width: 90vw;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.setting-content .content-body {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
=======
|
||||
>>>>>>> 2e0149f79 (🚧 Remove zoom/mobile option, rework header/misc layout to be more responsive)
|
||||
|
||||
/* Large displays and digital signage (1920px+) */
|
||||
@media screen and (min-width: 1920px) {
|
||||
body {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
|
||||
min-height: 56px;
|
||||
min-width: 56px;
|
||||
padding: 16px 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.minicard {
|
||||
min-height: 56px;
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 8px;
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
.board-canvas {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
#header {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
|
||||
#content > .wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
#modal .modal-content {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
|
||||
#modal .modal-content-wide {
|
||||
width: 1000px;
|
||||
}
|
||||
|
||||
|
||||
.setting-content .content-body {
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 320px;
|
||||
}
|
||||
}
|
||||
.inline-input {
|
||||
height: 37px;
|
||||
margin: 8px 10px 0 0;
|
||||
width: 100px;
|
||||
|
||||
.ui-sortable-handle {
|
||||
cursor: grab !important;
|
||||
}
|
||||
|
||||
.select-authentication {
|
||||
width: 100%;
|
||||
}
|
||||
.textBelowCustomLoginLogo,
|
||||
.auth-layout {
|
||||
#rescue-card-description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.auth-layout .auth-dialog {
|
||||
margin: 0 !important;
|
||||
flex: 1 0 auto;
|
||||
align-self: center;
|
||||
margin: 0 0.2lh;
|
||||
}
|
||||
.loadingText {
|
||||
text-align: center;
|
||||
|
|
@ -882,8 +878,18 @@ a:not(.disabled).is-active i.fa {
|
|||
text-decoration: underline;
|
||||
text-decoration-color: #17683a;
|
||||
}
|
||||
/*
|
||||
Prevents popups to compute real size, trying to comment
|
||||
.at-pwd-form, .at-sep, .at-oauth {
|
||||
display: none;
|
||||
}*/
|
||||
|
||||
#at-pwd-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: stretch;
|
||||
gap: 0.3lh;
|
||||
}
|
||||
@-moz-keyframes fadeIn {
|
||||
from {
|
||||
|
|
@ -928,31 +934,19 @@ a:not(.disabled).is-active i.fa {
|
|||
|
||||
/* iOS Safari Mobile Mode Fixes */
|
||||
@media screen and (max-width: 800px) {
|
||||
/* Prevent scrolling issues on iOS Safari when card popup is open */
|
||||
body.mobile-mode {
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* Fix z-index stacking for mobile Safari */
|
||||
body.mobile-mode .board-wrapper {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body.mobile-mode .board-wrapper .board-canvas .board-overlay {
|
||||
z-index: 17 !important;
|
||||
}
|
||||
|
||||
|
||||
body.mobile-mode .card-details {
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
|
||||
body.mobile-mode .pop-over {
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure smooth scrolling on iOS */
|
||||
body.mobile-mode .card-details,
|
||||
body.mobile-mode .pop-over .content-wrapper {
|
||||
|
|
|
|||
|
|
@ -23,61 +23,56 @@ template(name="main")
|
|||
//link(rel="stylesheet" type="text/css" class="__meteor-css__" href="css/html5-default-theme.css")
|
||||
|
||||
template(name="userFormsLayout")
|
||||
section.auth-layout
|
||||
if currentSetting.hideLogo
|
||||
h1.at-form-landing-logo
|
||||
br
|
||||
br
|
||||
unless currentSetting.hideLogo
|
||||
h1.at-form-landing-logo
|
||||
if currentSetting.customLoginLogoImageUrl
|
||||
if currentSetting.customLoginLogoLinkUrl
|
||||
a(href="{{currentSetting.customLoginLogoLinkUrl}}")
|
||||
img(src="{{currentSetting.customLoginLogoImageUrl}}" width="300" height="auto")
|
||||
.auth-container
|
||||
section.auth-layout.auth-logo
|
||||
if currentSetting.hideLogo
|
||||
h1.at-form-landing-logo
|
||||
unless currentSetting.hideLogo
|
||||
if currentSetting.customLoginLogoImageUrl
|
||||
if currentSetting.customLoginLogoLinkUrl
|
||||
a(href="{{currentSetting.customLoginLogoLinkUrl}}")
|
||||
img(src="{{currentSetting.customLoginLogoImageUrl}}")
|
||||
unless currentSetting.customLoginLogoLinkUrl
|
||||
a
|
||||
img(src="{{currentSetting.customLoginLogoImageUrl}}")
|
||||
else
|
||||
a
|
||||
img(src="{{pathFor '/wekan-logo.svg'}}" alt="")
|
||||
br
|
||||
unless currentSetting.customLoginLogoLinkUrl
|
||||
img(src="{{currentSetting.customLoginLogoImageUrl}}" width="300" height="auto")
|
||||
br
|
||||
else
|
||||
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
|
||||
br
|
||||
if currentSetting.textBelowCustomLoginLogo
|
||||
hr
|
||||
section.textBelowCustomLoginLogo
|
||||
+viewer
|
||||
| {{currentSetting.textBelowCustomLoginLogo}}
|
||||
hr
|
||||
section.auth-layout
|
||||
section.auth-dialog
|
||||
if isLoading
|
||||
+loader
|
||||
else
|
||||
// ARIA live region for error messages
|
||||
div#login-error-message(role="alert" aria-live="assertive" style="color: #d32f2f; margin-bottom: 1em;")
|
||||
+Template.dynamic(template=content)
|
||||
if currentSetting.displayAuthenticationMethod
|
||||
+connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
|
||||
if isLegalNoticeLinkExist
|
||||
div#legalNoticeDiv
|
||||
span#legalNoticeSpan {{_ 'acceptance_of_our_legalNotice'}}
|
||||
a#legalNoticeAtLink.at-link(href="{{currentSetting.legalNotice}}", target="_blank", rel="noopener noreferrer")
|
||||
| {{_ 'legalNotice'}}
|
||||
if getLegalNoticeWithWritTraduction
|
||||
div
|
||||
div.at-form-lang
|
||||
label(for="userform-set-language-select") {{_ 'changeLanguagePopup-title'}}
|
||||
select.select-lang.js-userform-set-language#userform-set-language-select(aria-label="{{_ 'changeLanguagePopup-title'}}")
|
||||
each languages
|
||||
if isCurrentLanguage
|
||||
if rtl
|
||||
option(value="{{tag}}" selected="selected") {{name}} (RTL)
|
||||
section.auth-custom-text
|
||||
if currentSetting.textBelowCustomLoginLogo
|
||||
section.textBelowCustomLoginLogo
|
||||
+viewer
|
||||
| {{currentSetting.textBelowCustomLoginLogo}}
|
||||
section.auth-layout.auth-form
|
||||
section.auth-dialog
|
||||
if isLoading
|
||||
+loader
|
||||
else
|
||||
// ARIA live region for error messages
|
||||
div#login-error-message(role="alert" aria-live="assertive" style="color: #d32f2f;")
|
||||
+Template.dynamic(template=content)
|
||||
if currentSetting.displayAuthenticationMethod
|
||||
+connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
|
||||
if isLegalNoticeLinkExist
|
||||
div#legalNoticeDiv
|
||||
span#legalNoticeSpan {{_ 'acceptance_of_our_legalNotice'}}
|
||||
a#legalNoticeAtLink.at-link(href="{{currentSetting.legalNotice}}", target="_blank", rel="noopener noreferrer")
|
||||
| {{_ 'legalNotice'}}
|
||||
div.at-form-lang
|
||||
label(for="userform-set-language-select") {{_ 'changeLanguagePopup-title'}}
|
||||
select.select-lang.js-userform-set-language#userform-set-language-select(aria-label="{{_ 'changeLanguagePopup-title'}}")
|
||||
each languages
|
||||
if isCurrentLanguage
|
||||
if rtl
|
||||
option(value="{{tag}}" selected="selected") {{name}} (RTL)
|
||||
else
|
||||
option(value="{{tag}}" selected="selected") {{name}}
|
||||
else
|
||||
option(value="{{tag}}" selected="selected") {{name}}
|
||||
else
|
||||
if rtl
|
||||
option(value="{{tag}}") {{name}} (RTL)
|
||||
else
|
||||
option(value="{{tag}}") {{name}}
|
||||
if rtl
|
||||
option(value="{{tag}}") {{name}} (RTL)
|
||||
else
|
||||
option(value="{{tag}}") {{name}}
|
||||
|
||||
template(name="defaultLayout")
|
||||
+header
|
||||
|
|
|
|||
|
|
@ -1,22 +1,18 @@
|
|||
.my-cards-board-wrapper {
|
||||
border-radius: 0 0 0.5vw 0.5vw;
|
||||
min-width: min(400px, 52vw);
|
||||
margin-bottom: 2.5vh;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
border-width: 0.3vw;
|
||||
border-style: solid;
|
||||
border-color: #a2a2a2;
|
||||
body.mobile-mode {
|
||||
.my-cards-board-wrapper {
|
||||
width: 100vw;
|
||||
}
|
||||
.my-cards-swimlane-body {
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
}
|
||||
.my-cards-board-title {
|
||||
font-size: clamp(1.2rem, 3vw, 1.6rem);
|
||||
font-weight: bold;
|
||||
padding: 0.7vh 0.7vw;
|
||||
background-color: #808080;
|
||||
color: #fff;
|
||||
.my-cards-swimlane-body {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 1ch;
|
||||
}
|
||||
.my-cards-swimlane-title {
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
font-size: clamp(1em, 2.5vw, 1.3rem);
|
||||
font-weight: bold;
|
||||
padding: 0.7vh 0.7vw;
|
||||
padding-bottom: 0.5vh;
|
||||
|
|
@ -27,48 +23,12 @@
|
|||
.swimlane-default-color {
|
||||
background-color: #d3d3d3;
|
||||
}
|
||||
.my-cards-list-title {
|
||||
font-weight: bold;
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
text-align: center;
|
||||
margin-bottom: 0.9vh;
|
||||
}
|
||||
.my-cards-list-wrapper {
|
||||
margin: 1.3vh 1.3vw;
|
||||
border-radius: 0.7vw;
|
||||
display: inline-grid;
|
||||
min-width: min(250px, 32vw);
|
||||
max-width: min(350px, 45vw);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: clamp(300px, 20vw, 30vw);
|
||||
}
|
||||
.my-cards-card-wrapper {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.3vh;
|
||||
}
|
||||
.my-cards-dueat-list-wrapper {
|
||||
max-width: min(500px, 65vw);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
.my-cards-board-table thead {
|
||||
border-bottom: 3px solid #4d4d4d;
|
||||
background-color: transparent;
|
||||
}
|
||||
.my-cards-board-table th,
|
||||
.my-cards-board-table td {
|
||||
border: 0;
|
||||
}
|
||||
.my-cards-board-table tr {
|
||||
border-bottom: 2px solid #a2a2a2;
|
||||
}
|
||||
.my-cards-card-title-table {
|
||||
font-weight: bold;
|
||||
padding-left: 2px;
|
||||
max-width: 243px;
|
||||
}
|
||||
.my-cards-board-badge {
|
||||
width: 36px;
|
||||
height: 24px;
|
||||
float: left;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
|
||||
body.mobile-mode .my-cards-list-wrapper {
|
||||
max-width: unset;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,15 +39,16 @@ template(name="myCards")
|
|||
.my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
|
||||
+viewer
|
||||
= swimlane.title
|
||||
each list in swimlane.myLists
|
||||
.my-cards-list-wrapper
|
||||
.my-cards-list-title(class=list.colorClass)
|
||||
+viewer
|
||||
= list.title
|
||||
each card in list.myCards
|
||||
.my-cards-card-wrapper
|
||||
a.minicard-wrapper(href=card.originRelativeUrl)
|
||||
+minicard(card)
|
||||
.my-cards-swimlane-body
|
||||
each list in swimlane.myLists
|
||||
.my-cards-list-wrapper
|
||||
.my-cards-list-title(class=list.colorClass)
|
||||
+viewer
|
||||
= list.title
|
||||
each card in list.myCards
|
||||
.my-cards-card-wrapper
|
||||
a.minicard-wrapper(href=card.originRelativeUrl)
|
||||
+minicard(card)
|
||||
if $eq myCardsView 'table'
|
||||
.wrapper
|
||||
table.my-cards-board-table
|
||||
|
|
|
|||
|
|
@ -1,91 +1,121 @@
|
|||
.pop-over {
|
||||
background: #fff;
|
||||
border-radius: 0.4vw;
|
||||
border: 1px solid #dbdbdb;
|
||||
background: #ededed;
|
||||
border-bottom-color: #c2c2c2;
|
||||
box-shadow: 0 0.2vh 0.8vh rgba(0,0,0,0.3);
|
||||
position: absolute;
|
||||
/* Wider default to fit full color palette */
|
||||
width: min(380px, 55vw);
|
||||
z-index: 99999;
|
||||
margin-top: 0.7vh;
|
||||
box-shadow: 0 0.2vh 0.8vh rgba(0, 0, 0, 0.3);
|
||||
/* so they can easily travel with mouse */
|
||||
position: fixed;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
resize: both;
|
||||
pointer-events: all;
|
||||
max-height: 100vh;
|
||||
|
||||
.content-wrapper {
|
||||
width: auto;
|
||||
height: auto;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content-wrapper >* {
|
||||
/* low specificity so that it can be transparently overriden,
|
||||
but could have side effects if no display is explicitely specific in inner content */
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.pop-over a:has(.fa-plus)+ :not(*) {
|
||||
min-height: 1.5lh;
|
||||
aspect-ratio: 1/1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 0.2lh;
|
||||
}
|
||||
.pop-over hr {
|
||||
margin: 0.5vh 0px;
|
||||
margin: 0.3lh 0;
|
||||
/* below everything in the same stacking context when
|
||||
after, child or explicit z-index */
|
||||
z-index: 0;
|
||||
}
|
||||
.pop-over p,
|
||||
.pop-over textarea,
|
||||
.pop-over input[type="text"],
|
||||
.pop-over input[type="email"],
|
||||
.pop-over input[type="password"],
|
||||
.pop-over input[type="file"] {
|
||||
width: 100%;
|
||||
.pop-over {
|
||||
/* feels like it's too ad-hod */
|
||||
input, a:not(.js-board-template, .member, .edit-avatar) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1ch;
|
||||
min-height: 1.5lh;
|
||||
}
|
||||
}
|
||||
.pop-over select {
|
||||
width: 100%;
|
||||
margin-bottom: 1.8vh;
|
||||
}
|
||||
.pop-over textarea {
|
||||
height: 9vh;
|
||||
}
|
||||
.pop-over form a span {
|
||||
padding: 0 0.7vw;
|
||||
.pop-over .sub-name {
|
||||
max-width: clamp(30vw, 500px, 80%);
|
||||
}
|
||||
.pop-over .header {
|
||||
height: 4.5vh;
|
||||
position: relative;
|
||||
margin-bottom: 1vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1ch;
|
||||
align-items: center;
|
||||
padding: 0 1ch;
|
||||
background: #f7f7f7;
|
||||
border-bottom: 1px solid #dcdcdc;
|
||||
color: #666;
|
||||
min-height: 2lh;
|
||||
}
|
||||
.pop-over .header .header-title {
|
||||
display: block;
|
||||
line-height: 4vh;
|
||||
padding-top: 0.5vh;
|
||||
margin: 0 1.3vw;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 1.2em;
|
||||
flex: 1;
|
||||
cursor: grab !important;
|
||||
}
|
||||
.pop-over .header .back-btn {
|
||||
.pop-over .back-btn {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
width: 4vw;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
.pop-over .header .back-btn i.fa {
|
||||
margin: 1.3vw;
|
||||
margin-top: 1.5vh;
|
||||
}
|
||||
.pop-over .header .back-btn.is-hidden {
|
||||
.pop-over .back-btn.is-hidden {
|
||||
width: 0;
|
||||
}
|
||||
.pop-over .header .close-btn {
|
||||
padding: 1.3vh 1.3vw 1.3vh 0.5vw;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.pop-over.no-title .header {
|
||||
background: none;
|
||||
}
|
||||
.pop-over .content-wrapper {
|
||||
width: 100%;
|
||||
max-height: calc(70vh + 20px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
.pop-over {
|
||||
.content-wrapper, .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allow dynamic max-height to override default constraint */
|
||||
.pop-over[style*="max-height"] .content-wrapper {
|
||||
max-height: inherit;
|
||||
.pop-over:has(.header) .content {
|
||||
/* inner content has full width available,
|
||||
so it is also responsive for margins, sizes, etc */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.popup-placeholder {
|
||||
/* This gives relative coordinates but height/width cannot fit the parent's
|
||||
without it having position: relative; we need to get them programmatically */
|
||||
position: absolute;
|
||||
/* Take all size of parent so it can be useful in computations */
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pop-over .content-container {
|
||||
width: 100%;
|
||||
max-height: calc(70vh + 20px);
|
||||
transition: transform 0.2s;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Allow dynamic max-height to override default constraint for content-container */
|
||||
|
|
@ -93,270 +123,42 @@
|
|||
max-height: inherit;
|
||||
}
|
||||
|
||||
/* Fix overflow in the Member Settings (member menu) popup:
|
||||
the popup itself gets a max-height inline style, but the header consumes space.
|
||||
Make the header overlay the scrollable area so the list can't spill out. */
|
||||
.pop-over[data-popup="memberMenuPopup"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
.pop-over[data-popup="memberMenuPopup"] > .header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.pop-over[data-popup="memberMenuPopup"] > .content-wrapper {
|
||||
padding-top: calc(4.5vh + 1vh);
|
||||
box-sizing: border-box;
|
||||
.pop-over .popup-drag-handle {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
/* Admin edit popups: use full height */
|
||||
.pop-over[data-popup="editUserPopup"],
|
||||
.pop-over[data-popup="editOrgPopup"],
|
||||
.pop-over[data-popup="editTeamPopup"] {
|
||||
height: calc(100vh - 20px) !important;
|
||||
max-height: calc(100vh - 20px) !important;
|
||||
body.mobile-mode {
|
||||
.popup-drag-handle, .close-btn {
|
||||
font-size: 1.4em;
|
||||
align-self: center;
|
||||
}
|
||||
.pop-over:has(.pop-over-list) {
|
||||
min-width: 70vw;
|
||||
}
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editUserPopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editOrgPopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editTeamPopup"] .content-wrapper {
|
||||
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
||||
height: calc(100vh - 80px) !important;
|
||||
overflow-y: auto !important;
|
||||
.pop-over .header-controls {
|
||||
display: flex;
|
||||
gap: 1ch;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editUserPopup"] .content-container,
|
||||
.pop-over[data-popup="editOrgPopup"] .content-container,
|
||||
.pop-over[data-popup="editTeamPopup"] .content-container {
|
||||
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
||||
height: calc(100vh - 80px) !important;
|
||||
}
|
||||
|
||||
/* Ensure language popup list can scroll properly */
|
||||
.pop-over .pop-over-list {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Specific styling for language popup list */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .pop-over-list {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Ensure content div in language popup contains all items */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content {
|
||||
height: auto;
|
||||
/* Remove forced min-height to avoid top gap */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
font-size: 1.1rem;
|
||||
padding: 0 1ch;
|
||||
>li>a {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: fit-content;
|
||||
justify-content: start;
|
||||
padding: 0 0.5ch;
|
||||
column-gap: 1ch;
|
||||
.sub-name {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure hidden stack pages truly take no space */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content.no-height {
|
||||
min-height: 0 !important;
|
||||
height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
/* Make language popup extend to bottom of browser window */
|
||||
.pop-over[data-popup="changeLanguagePopup"] {
|
||||
position: fixed !important;
|
||||
bottom: 0 !important;
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
right: 20px !important;
|
||||
width: auto !important;
|
||||
max-width: 450px !important;
|
||||
height: 100vh !important;
|
||||
max-height: 100vh !important;
|
||||
min-height: 300px !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Allow dynamic height for Change Language popup */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .header {
|
||||
flex-shrink: 0 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content-wrapper {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
height: auto !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content-container {
|
||||
height: auto !important;
|
||||
max-height: none !important;
|
||||
flex: 1 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content {
|
||||
height: auto !important;
|
||||
max-height: none !important;
|
||||
padding-bottom: 50px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Date popup sizing for native HTML inputs */
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"],
|
||||
.pop-over[data-popup="editCardStartDatePopup"],
|
||||
.pop-over[data-popup="editCardDueDatePopup"],
|
||||
.pop-over[data-popup="editCardEndDatePopup"],
|
||||
.pop-over[data-popup*="Date"] {
|
||||
width: min(400px, 90vw) !important; /* Smaller width for native inputs */
|
||||
min-width: 350px !important;
|
||||
max-height: 80vh !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup*="Date"] .content-wrapper {
|
||||
max-height: 60vh !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .content-container,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .content-container,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .content-container,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .content-container,
|
||||
.pop-over[data-popup*="Date"] .content-container {
|
||||
max-height: 60vh !important;
|
||||
}
|
||||
|
||||
/* Native HTML input styling */
|
||||
.pop-over[data-popup*="Date"] .datepicker-container {
|
||||
width: 100% !important;
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container .fields {
|
||||
display: flex !important;
|
||||
gap: 15px !important;
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container .fields .left,
|
||||
.pop-over[data-popup*="Date"] .datepicker-container .fields .right {
|
||||
flex: 1 !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container label {
|
||||
display: block !important;
|
||||
margin-bottom: 5px !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="date"],
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="time"] {
|
||||
width: 100% !important;
|
||||
padding: 8px !important;
|
||||
border: 1px solid #ccc !important;
|
||||
border-radius: 4px !important;
|
||||
font-size: 14px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="date"]:focus,
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="time"]:focus {
|
||||
outline: none !important;
|
||||
border-color: #007cba !important;
|
||||
box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Ensure date popup buttons stay within popup boundaries */
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .content,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .content,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .content,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .content,
|
||||
.pop-over[data-popup*="Date"] .content {
|
||||
max-height: 60vh !important; /* Leave space for buttons */
|
||||
overflow-y: auto !important;
|
||||
padding-bottom: 100px !important; /* More space for buttons */
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup*="Date"] .datepicker-container {
|
||||
max-height: 50vh !important; /* Limit calendar height */
|
||||
overflow-y: auto !important;
|
||||
margin-bottom: 20px !important; /* Space before buttons */
|
||||
}
|
||||
|
||||
/* Ensure buttons are properly positioned */
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date,
|
||||
.pop-over[data-popup*="Date"] .edit-date {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup*="Date"] .edit-date .fields {
|
||||
flex-shrink: 0 !important;
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup*="Date"] .edit-date .js-datepicker {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup*="Date"] .edit-date button {
|
||||
flex-shrink: 0 !important;
|
||||
margin-top: 15px !important;
|
||||
position: relative !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
.pop-over .content-container .content {
|
||||
/* Match wider popover, leave padding */
|
||||
width: 100%;
|
||||
padding: 0 1.3vw 1.3vh;
|
||||
box-sizing: border-box;
|
||||
/* Ensure content is not shifted left */
|
||||
margin-left: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Utility: remove left gutter inside specific popups */
|
||||
.pop-over .content .flush-left {
|
||||
margin-left: 0;
|
||||
|
|
@ -378,58 +180,15 @@
|
|||
.pop-over .content form.create-label .palette-colors {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
|
||||
/* Color palette items: ensure proper positioning */
|
||||
.pop-over .content .palette-colors .palette-color {
|
||||
margin-left: 0;
|
||||
margin-right: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Global fix for all popup content to prevent left shifting */
|
||||
.pop-over .content * {
|
||||
margin-left: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Override any potential left shifting for specific elements */
|
||||
.pop-over .content form,
|
||||
.pop-over .content .palette-colors,
|
||||
.pop-over .content .pop-over-list,
|
||||
.pop-over .content .flush-left {
|
||||
margin-left: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Fix popup depth containers that cause left shifting */
|
||||
.pop-over .popup-container-depth-1,
|
||||
.pop-over .popup-container-depth-2,
|
||||
.pop-over .popup-container-depth-3,
|
||||
.pop-over .popup-container-depth-4,
|
||||
.pop-over .popup-container-depth-5,
|
||||
.pop-over .popup-container-depth-6 {
|
||||
transform: none !important;
|
||||
margin-left: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure buttons don’t reserve left space; align to flow */
|
||||
.pop-over .content form.swimlane-color-popup .primary.confirm,
|
||||
.pop-over .content form.swimlane-color-popup .negate.wide.right,
|
||||
.pop-over .content .swimlane-height-popup .primary.confirm,
|
||||
.pop-over .content .swimlane-height-popup .negate.wide.right {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
.pop-over .content-container .content.no-height {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
visibility: hidden;
|
||||
border-radius: 0;
|
||||
outline: 0.1ch solid black;
|
||||
}
|
||||
.pop-over.search-over {
|
||||
background: #f0f0f0;
|
||||
|
|
@ -456,24 +215,6 @@
|
|||
.pop-over .sk-spinner {
|
||||
margin: 40px auto;
|
||||
}
|
||||
.pop-over .popup-container-depth-1 {
|
||||
transform: translateX(-300px);
|
||||
}
|
||||
.pop-over .popup-container-depth-2 {
|
||||
transform: translateX(-600px);
|
||||
}
|
||||
.pop-over .popup-container-depth-3 {
|
||||
transform: translateX(-900px);
|
||||
}
|
||||
.pop-over .popup-container-depth-4 {
|
||||
transform: translateX(-1200px);
|
||||
}
|
||||
.pop-over .popup-container-depth-5 {
|
||||
transform: translateX(-1500px);
|
||||
}
|
||||
.pop-over .popup-container-depth-6 {
|
||||
transform: translateX(-1800px);
|
||||
}
|
||||
.select-members-list,
|
||||
.select-avatars-list {
|
||||
margin-bottom: 8px;
|
||||
|
|
@ -487,15 +228,12 @@
|
|||
cursor: pointer;
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
padding: 1.5px 10px;
|
||||
padding-inline: 2vmin 10vmin;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
line-height: 33px;
|
||||
display:flex;
|
||||
/* flex-wrap:wrap;*/
|
||||
gap:5px;
|
||||
align-items: center;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
|
@ -506,7 +244,6 @@
|
|||
.pop-over-list li > a .item-name {
|
||||
display: block;
|
||||
width: auto;
|
||||
padding-right: 22px;
|
||||
}
|
||||
.pop-over-list li > a:not(.disabled):hover {
|
||||
background-color: #005377;
|
||||
|
|
@ -522,9 +259,9 @@
|
|||
.pop-over-list li > a .sub-name {
|
||||
color: #8c8c8c;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 400;
|
||||
line-height: 15px;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
.pop-over-list li > a.current {
|
||||
background-color: #e2e6e9;
|
||||
|
|
@ -570,156 +307,21 @@
|
|||
body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
|
||||
color: #7a7a7a;
|
||||
}
|
||||
.pop-over.miniprofile .header {
|
||||
border-bottom-color: transparent;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
z-index: 1;
|
||||
}
|
||||
.pop-over.miniprofile .header-title {
|
||||
display: none;
|
||||
}
|
||||
.pop-over.miniprofile .pop-over-list {
|
||||
padding-top: 8px;
|
||||
}
|
||||
.pop-over.miniprofile .miniprofile-header {
|
||||
margin-top: 8px;
|
||||
min-height: 56px;
|
||||
position: relative;
|
||||
}
|
||||
.pop-over.miniprofile .miniprofile-header .member,
|
||||
.pop-over.miniprofile .miniprofile-header .avatar {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
.pop-over.miniprofile .miniprofile-header .info {
|
||||
margin: 0 0 0 64px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.pop-over.miniprofile .miniprofile-header .info h3 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.pop-over.miniprofile .miniprofile-header .info h3 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.pop-over {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin-top: 0px;
|
||||
border: 0px solid #dbdbdb;
|
||||
/* Ensure popups appear above card details on mobile */
|
||||
z-index: 999999 !important;
|
||||
/* iOS Safari scrolling fix */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.pop-over .header {
|
||||
color: #fff;
|
||||
background: #2980b9;
|
||||
height: 48px;
|
||||
padding: 0px 0px;
|
||||
border: 0px;
|
||||
margin: 0px 0px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
}
|
||||
.pop-over .header .header-title {
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.pop-over .header .back-btn {
|
||||
width: 30px;
|
||||
padding: 8px 12px 8px 12px;
|
||||
}
|
||||
.pop-over .header .back-btn i.fa {
|
||||
color: #fff;
|
||||
}
|
||||
.pop-over .header .close-btn {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.pop-over .header .close-btn i.fa {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
.pop-over .content-wrapper {
|
||||
width: 100%;
|
||||
height: calc(100% - 48px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
margin: 48px 0px 0px 0px;
|
||||
}
|
||||
.pop-over .content-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
.pop-over .content-container .content {
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
}
|
||||
.pop-over .content-container .content form {
|
||||
margin: 10px 10px;
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
.pop-over .content-container .content p,
|
||||
.pop-over .content-container .content textarea,
|
||||
.pop-over .content-container .content input[type="text"],
|
||||
.pop-over .content-container .content input[type="email"],
|
||||
.pop-over .content-container .content input[type="password"],
|
||||
.pop-over .content-container .content input[type="file"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.pop-over .pop-over-list li > a {
|
||||
width: calc(100% - 20px);
|
||||
margin: 0px 0px;
|
||||
}
|
||||
.pop-over .popup-container-depth-1 {
|
||||
transform: none !important;
|
||||
}
|
||||
.pop-over .popup-container-depth-2 {
|
||||
transform: none !important;
|
||||
}
|
||||
.pop-over .popup-container-depth-3 {
|
||||
transform: none !important;
|
||||
}
|
||||
.pop-over .popup-container-depth-4 {
|
||||
transform: none !important;
|
||||
}
|
||||
.pop-over .popup-container-depth-5 {
|
||||
transform: none !important;
|
||||
}
|
||||
.pop-over .popup-container-depth-6 {
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.pop-over .content > form {
|
||||
padding: 0 1ch;
|
||||
gap: 0.2lh;
|
||||
display: flex;
|
||||
max-width: clamp(20vw, 400px, 50vw);
|
||||
}
|
||||
|
||||
/* Force full-screen popups in mobile mode regardless of screen width */
|
||||
body.mobile-mode .pop-over {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
max-width: 100vw !important;
|
||||
max-height: 100vh !important;
|
||||
}
|
||||
body.mobile-mode .pop-over .content-wrapper {
|
||||
width: 100% !important;
|
||||
height: calc(100vh - 48px) !important;
|
||||
max-height: calc(100vh - 48px) !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
body.mobile-mode .pop-over .content>form {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.pop-over .board-subtask-settings {
|
||||
>h3 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +1,696 @@
|
|||
Popup.template.events({
|
||||
'click .js-back-view'() {
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-close-pop-over'() {
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-confirm'() {
|
||||
this.__afterConfirmAction.call(this);
|
||||
},
|
||||
// This handler intends to solve a pretty tricky bug with our popup
|
||||
// transition. The transition is implemented using a large container
|
||||
// (.content-container) that is moved on the x-axis (from 0 to n*PopupSize)
|
||||
// inside a wrapper (.container-wrapper) with a hidden overflow. The problem
|
||||
// is that sometimes the wrapper is scrolled -- even if there are no
|
||||
// scrollbars. This happen for instance when the newly opened popup has some
|
||||
// focused field, the browser will automatically scroll the wrapper, resulting
|
||||
// in moving the whole popup container outside of the popup wrapper. To
|
||||
// disable this behavior we have to manually reset the scrollLeft position
|
||||
// whenever it is modified.
|
||||
'scroll .content-wrapper'(evt) {
|
||||
evt.currentTarget.scrollLeft = 0;
|
||||
},
|
||||
});
|
||||
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
|
||||
import { Template } from 'meteor/templating';
|
||||
|
||||
// When a popup content is removed (ie, when the user press the "back" button),
|
||||
// we need to wait for the container translation to end before removing the
|
||||
// actual DOM element. For that purpose we use the undocumented `_uihooks` API.
|
||||
Popup.template.onRendered(() => {
|
||||
const container = this.find('.content-container');
|
||||
container._uihooks = {
|
||||
removeElement(node) {
|
||||
$(node).addClass('no-height');
|
||||
$(container).one(CSSEvents.transitionend, () => {
|
||||
node.parentNode.removeChild(node);
|
||||
const PopupBias = {
|
||||
Before: Symbol("S"),
|
||||
Overlap: Symbol("M"),
|
||||
After: Symbol("A"),
|
||||
Fullscreen: Symbol("F"),
|
||||
includes(e) {
|
||||
return Object.values(this).includes(e);
|
||||
}
|
||||
}
|
||||
|
||||
// this class is a bit cumbersome and could probably be done simpler.
|
||||
// it manages two things : initial placement and sizing given an opener element,
|
||||
// and then movement and resizing. one difficulty was to be able, as a popup
|
||||
// which can be resized from the "outside" (CSS4) and move from the inside (inner
|
||||
// component), which also grows and shrinks frequently, to adapt.
|
||||
// I tried many approach and failed to get the perfect fit; I feel that there is
|
||||
// always something indeterminate at some point. so the only drawback is that
|
||||
// if a popup contains another resizable component (e.g. card details), and if
|
||||
// it has been resized (with CSS handle), it will lose its dimensions when dragging
|
||||
// it next time.
|
||||
class PopupDetachedComponent extends BlazeComponent {
|
||||
onCreated() {
|
||||
// Set by parent/caller (usually PopupComponent)
|
||||
({ nonPlaceholderOpener: this.nonPlaceholderOpener, closeDOMs: this.closeDOMs = [], followDOM: this.followDOM } = this.data());
|
||||
|
||||
|
||||
if (typeof(this.closeDOMs) === "string") {
|
||||
// helper for passing arg in JADE template
|
||||
this.closeDOMs = this.closeDOMs.split(';');
|
||||
}
|
||||
|
||||
// The popup's own header, if it exists
|
||||
this.closeDOMs.push("click .js-close-detached-popup");
|
||||
}
|
||||
|
||||
// Main intent of this component is to have a modular popup with defaults:
|
||||
// - sticks to its opener while being a child of body (thus in the same stacking context, no z-index issue)
|
||||
// - is responsive on shrink while keeping position absolute
|
||||
// - can grow back to initial position step by step
|
||||
// - exposes various sizes as CSS variables so each rendered popup can use them to adapt defaults
|
||||
// * issue is that it is done by hand, with heurisitic/simple algorithm from my thoughts, not sure it covers edge cases
|
||||
// * however it works well so far and maybe more "fixed" element should be popups
|
||||
onRendered() {
|
||||
// Remember initial ratio between initial dimensions and viewport
|
||||
const viewportHeight = window.innerHeight;
|
||||
const viewportWidth = window.innerWidth;
|
||||
|
||||
this.popup = this.firstNode();
|
||||
this.popupOpener = this.data().openerElement;
|
||||
|
||||
const popupStyle = window.getComputedStyle(this.firstNode());
|
||||
// margin may be in a relative unit, not computable in JS, but we get the actual pixels here
|
||||
this.popupMargin = parseFloat(popupStyle.getPropertyValue("--popup-margin"), 10) || Math.min(window.innerWidth / 50, window.innerHeight / 50);
|
||||
|
||||
this.dims(this.computeMaxDims());
|
||||
|
||||
this.initialPopupWidth = this.popupDims.width;
|
||||
this.initialPopupHeight = this.popupDims.height;
|
||||
this.initialHeightRatio = this.initialPopupHeight / viewportHeight;
|
||||
this.initialWidthRatio = this.initialPopupWidth / viewportWidth;
|
||||
|
||||
this.dims(this.computePopupDims());
|
||||
|
||||
|
||||
if (this.followDOM) {
|
||||
this.innerElement = this.find(this.followDOM) ?? document.querySelector(this.followDOM);
|
||||
}
|
||||
|
||||
this.follow();
|
||||
this.toFront();
|
||||
|
||||
// #FIXME the idea of keeping the initial ratio on resize is quite bad. remove that part.
|
||||
// there is a reactive variable for window resize in Utils, but the interface is too slow
|
||||
// with all reactive stuff, use events when possible and when not really bypassing logic
|
||||
$(window).on('resize', () => {
|
||||
// #FIXME there is a bug when window grows; popup outer container
|
||||
// will grow beyond the size of content and it's not easy to fix for me (and I feel tired of this popup)
|
||||
this.dims(this.computePopupDims());
|
||||
});
|
||||
}
|
||||
|
||||
margin() {
|
||||
return this.popupMargin;
|
||||
}
|
||||
|
||||
ensureDimsLimit(dims) {
|
||||
// boilerplate to make sure that popup visually fits
|
||||
let { left, top, width, height } = dims;
|
||||
let overflowBottom = top + height + 2 * this.margin() - window.innerHeight;
|
||||
let overflowRight = left + width + 2 * this.margin() - window.innerWidth;
|
||||
if (overflowRight > 0) {
|
||||
width = Math.max(20 * this.margin(), Math.min(width - overflowRight, window.innerWidth - 2 * this.margin()));
|
||||
}
|
||||
if (overflowBottom > 0) {
|
||||
height = Math.max(10 * this.margin(), Math.min(height - overflowBottom, window.innerHeight - 2 * this.margin()));
|
||||
}
|
||||
left = Math.max(left, this.margin());
|
||||
top = Math.max(top, this.margin());
|
||||
return { left, top, width, height }
|
||||
}
|
||||
|
||||
dims(newDims) {
|
||||
if (!this.popupDims) {
|
||||
this.popupDims = {};
|
||||
}
|
||||
if (newDims) {
|
||||
newDims = this.ensureDimsLimit(newDims);
|
||||
for (const e of Object.keys(newDims)) {
|
||||
let value = parseFloat(newDims[e]);
|
||||
if (!isNaN(value)) {
|
||||
$(this.popup).css(e, `${value}px`);
|
||||
this.popupDims[e] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.popupDims;
|
||||
}
|
||||
|
||||
isFullscreen() {
|
||||
return this.fullscreen;
|
||||
}
|
||||
|
||||
maximize() {
|
||||
this.fullscreen = true;
|
||||
this.dims(this.computePopupDims());
|
||||
if (this.innerElement) {
|
||||
$(this.innerElement).css('width', '');
|
||||
$(this.innerElement).css('height', '')
|
||||
}
|
||||
}
|
||||
|
||||
minimize() {
|
||||
this.fullscreen = false;
|
||||
this.dims(this.computePopupDims());
|
||||
}
|
||||
|
||||
follow() {
|
||||
const adaptChild = new ResizeObserver((_) => {
|
||||
if (this.fullscreen) {return}
|
||||
const width = this.innerElement?.scrollWidth || this.popup.scrollWidth;
|
||||
const height = this.innerElement?.scrollHeight || this.popup.scrollHeight;
|
||||
// we don't want to run this during something that we have caused, eg. dragging
|
||||
if (!this.mouseDown) {
|
||||
// extra-"future-proof" stuff: if somebody adds a margin to the popup, it would trigger a loop
|
||||
if (Math.abs(this.dims().width - width) < 20 && Math.abs(this.dims().height - height) < 20) { return }
|
||||
|
||||
// if inner shrinks, follow
|
||||
if (width < this.dims().width || height < this.dims().height) {
|
||||
this.dims({ width, height });
|
||||
}
|
||||
// otherwise it may be complicated to find a generic situation, but we have the
|
||||
// classic positionning procedure which works, so use it and ignore positionning
|
||||
else {
|
||||
const newDims = this.computePopupDims();
|
||||
// a bit twisted/ad-hoc for card details, in the edge case where they are opened when collapsed then uncollapsed,
|
||||
// not sure to understand why the sizing works differently that starting uncollapsed then doing the same sequence
|
||||
this.dims(this.ensureDimsLimit({
|
||||
top: this.dims().top,
|
||||
left: this.dims().left,
|
||||
width: Math.max(newDims.width, width),
|
||||
height: Math.max(newDims.height, height)
|
||||
}));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const { width, height } = this.popup.getBoundingClientRect();
|
||||
// only case when we bypass .dims(), to avoid loop
|
||||
this.popupDims.width = width;
|
||||
this.popupDims.height = height;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.innerElement) {
|
||||
adaptChild.observe(this.innerElement);
|
||||
} else {
|
||||
adaptChild.observe(this.popup);
|
||||
}
|
||||
}
|
||||
|
||||
currentZ(z = undefined) {
|
||||
// relative, add a constant to be above root elements
|
||||
if (z !== undefined) {
|
||||
this.firstNode().style.zIndex = parseInt(z) + 10;
|
||||
}
|
||||
return parseInt(this.firstNode().style.zIndex) - 10;
|
||||
}
|
||||
|
||||
// a bit complex...
|
||||
toFront() {
|
||||
this.currentZ(Math.max(...PopupComponent.stack.map(p => BlazeComponent.getComponentForElement(p.outerView.firstNode()).currentZ())) || 0 + 1);
|
||||
|
||||
}
|
||||
|
||||
toBack() {
|
||||
this.currentZ(Math.min(...PopupComponent.stack.map(p => BlazeComponent.getComponentForElement(p.outerView.firstNode()).currentZ())) || 1 - 1);
|
||||
}
|
||||
|
||||
events() {
|
||||
// needs to be done at this level; "parent" is not a parent in DOM
|
||||
let closeEvents = {};
|
||||
|
||||
this.closeDOMs?.forEach((e) => {
|
||||
closeEvents[e] = (_) => {
|
||||
this.parentComponent().destroy();
|
||||
}
|
||||
})
|
||||
|
||||
const miscEvents = {
|
||||
'click .js-confirm'() {
|
||||
this.data().afterConfirm?.call(this);
|
||||
},
|
||||
// bad heuristic but only for best-effort UI
|
||||
'pointerdown .pop-over'() {
|
||||
this.mouseDown = true;
|
||||
},
|
||||
'pointerup .pop-over'() {
|
||||
this.mouseDown = false;
|
||||
}
|
||||
};
|
||||
|
||||
const movePopup = (event) => {
|
||||
event.preventDefault();
|
||||
$(event.target).addClass('is-active');
|
||||
const deltaHandleX = this.dims().left - event.clientX;
|
||||
const deltaHandleY = this.dims().top - event.clientY;
|
||||
|
||||
const onPointerMove = (e) => {
|
||||
this.dims(this.ensureDimsLimit({ left: e.clientX + deltaHandleX, top: e.clientY + deltaHandleY, width: this.dims().width, height: this.dims().height }));
|
||||
|
||||
if (this.popup.scrollY) {
|
||||
this.popup.scrollTo(0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (event) => {
|
||||
$(document).off('pointermove', onPointerMove);
|
||||
$(document).off('pointerup', onPointerUp);
|
||||
$(event.target).removeClass('is-active');
|
||||
};
|
||||
|
||||
if (Utils.shouldIgnorePointer(event)) {
|
||||
onPointerUp(event);
|
||||
return;
|
||||
}
|
||||
|
||||
$(document).on('pointermove', onPointerMove);
|
||||
$(document).on('pointerup', onPointerUp);
|
||||
};
|
||||
|
||||
// We do not manage dragging without our own header
|
||||
const handleDOM = this.data().handleDOM;
|
||||
if (this.data().showHeader) {
|
||||
const handleSelector = Utils.isMiniScreen() ? '.js-popup-drag-handle' : '.header-title';
|
||||
miscEvents[`pointerdown ${handleSelector}`] = (e) => movePopup(e);
|
||||
}
|
||||
if (handleDOM) {
|
||||
miscEvents[`pointerdown ${handleDOM}`] = (e) => movePopup(e);
|
||||
}
|
||||
return super.events().concat(closeEvents).concat(miscEvents);
|
||||
}
|
||||
|
||||
computeMaxDims() {
|
||||
// Get size of inner content, even if it overflows
|
||||
const content = this.find('.content');
|
||||
let popupHeight = content.scrollHeight;
|
||||
let popupWidth = content.scrollWidth;
|
||||
if (this.data().showHeader) {
|
||||
const headerRect = this.find('.header');
|
||||
popupHeight += headerRect.scrollHeight;
|
||||
popupWidth = Math.max(popupWidth, headerRect.scrollWidth)
|
||||
}
|
||||
return { width: Math.max(popupWidth, $(this.popup).width()), height: Math.max(popupHeight, $(this.popup).height()) };
|
||||
|
||||
}
|
||||
|
||||
placeOnSingleDimension(elementLength, openerPos, openerLength, maxLength, biases, n) {
|
||||
// avoid too much recursion if no solution
|
||||
if (!n) {
|
||||
n = 0;
|
||||
}
|
||||
if (n >= 5) {
|
||||
// if we exhausted a bias, remove it
|
||||
n = 0;
|
||||
biases.pop();
|
||||
if (biases.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
n += 1;
|
||||
}
|
||||
|
||||
if (!biases?.length) {
|
||||
const cut = maxLength / 3;
|
||||
|
||||
if (openerPos < cut) {
|
||||
// Corresponds to the default ordering: if element is close to the axe's start,
|
||||
// try to put the popup after it; then to overlap; and give up otherwise.
|
||||
biases = [PopupBias.After, PopupBias.Overlap]
|
||||
}
|
||||
else if (openerPos > 2 * cut) {
|
||||
// Same idea if popup is close to the end
|
||||
biases = [PopupBias.Before, PopupBias.Overlap]
|
||||
}
|
||||
else {
|
||||
// If in the middle, try to overlap: choosing between start or end, even for
|
||||
// default, is too arbitrary; a custom order can be passed in argument.
|
||||
biases = [PopupBias.Overlap]
|
||||
}
|
||||
}
|
||||
// Remove the first element and get it
|
||||
const bias = biases.splice(0, 1)[0];
|
||||
|
||||
let factor;
|
||||
const openerRef = openerPos + openerLength / 2;
|
||||
if (bias === PopupBias.Before) {
|
||||
factor = 1;
|
||||
}
|
||||
else if (bias === PopupBias.Overlap) {
|
||||
factor = openerRef / maxLength;
|
||||
}
|
||||
else {
|
||||
factor = 0;
|
||||
}
|
||||
|
||||
let candidatePos = openerRef - elementLength * factor;
|
||||
const deltaMax = candidatePos + elementLength - maxLength;
|
||||
if (candidatePos < 0 || deltaMax > 0) {
|
||||
if (deltaMax <= 2 * this.margin()) {
|
||||
// if this is just a matter of margin, try again
|
||||
// useful for (literal) corner cases
|
||||
biases = [bias].concat(biases);
|
||||
openerPos -= 5;
|
||||
}
|
||||
if (biases.length === 0) {
|
||||
// we could have returned candidate position even if the size is too large, so
|
||||
// that the caller can choose, but it means more computations and edge cases...
|
||||
// any negative means fullscreen overall as the caller will take the maximum between
|
||||
// margin and candidate.
|
||||
return -1;
|
||||
}
|
||||
return this.placeOnSingleDimension(elementLength, openerPos, openerLength, maxLength, biases, n);
|
||||
}
|
||||
return candidatePos;
|
||||
}
|
||||
|
||||
computePopupDims() {
|
||||
if (!this.isRendered?.()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Coordinates of opener related to viewport
|
||||
let { x: parentX, y: parentY } = this.nonPlaceholderOpener.getBoundingClientRect();
|
||||
let { height: parentHeight, width: parentWidth } = this.nonPlaceholderOpener.getBoundingClientRect();
|
||||
|
||||
// Initial dimensions scaled to the viewport, if it has changed
|
||||
let popupHeight = window.innerHeight * this.initialHeightRatio;
|
||||
let popupWidth = window.innerWidth * this.initialWidthRatio;
|
||||
|
||||
if (this.fullscreen || Utils.isMiniScreen() && popupWidth >= 4 * window.innerWidth / 5 && popupHeight >= 4 * window.innerHeight / 5) {
|
||||
// Go fullscreen!
|
||||
popupWidth = window.innerWidth;
|
||||
// Avoid address bar, let a bit of margin to scroll
|
||||
popupHeight = 4 * window.innerHeight / 5;
|
||||
return ({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
left: 0,
|
||||
top: 0,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Current viewport dimensions
|
||||
let maxHeight = window.innerHeight - this.margin() * 2;
|
||||
let maxWidth = window.innerWidth - this.margin() * 2;
|
||||
let biasX, biasY;
|
||||
if (Utils.isMiniScreen()) {
|
||||
// On mobile I found that being able to close a popup really close from where it has been clicked
|
||||
// is comfortable; so given that the close button is top-right, we prefer the position of
|
||||
// popup being right-bottom, when possible. We then try every position, rather than choosing
|
||||
// relatively to the relative position of opener in viewport
|
||||
biasX = [PopupBias.Before, PopupBias.Overlap, PopupBias.After];
|
||||
biasY = [PopupBias.After, PopupBias.Overlap, PopupBias.Before];
|
||||
}
|
||||
|
||||
const candidateX = this.placeOnSingleDimension(popupWidth, parentX, parentWidth, maxWidth, biasX);
|
||||
const candidateY = this.placeOnSingleDimension(popupHeight, parentY, parentHeight, maxHeight, biasY);
|
||||
|
||||
// Reasonable defaults that can be overriden by CSS later: popups are tall, try to fit the reste
|
||||
// of the screen starting from parent element, or full screen if element if not fitting
|
||||
return ({
|
||||
width: popupWidth,
|
||||
height: popupHeight,
|
||||
left: candidateX,
|
||||
top: candidateY,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PopupComponent extends BlazeComponent {
|
||||
static stack = [];
|
||||
// good enough as long as few occurences of such cases
|
||||
static multipleBlacklist = ["cardDetails"];
|
||||
|
||||
// to provide compatibility with Popup.open().
|
||||
static open(args) {
|
||||
const openerView = Blaze.getView(args.openerElement);
|
||||
if (!openerView) {
|
||||
console.warn(`no parent found for popup ${args.name}, attaching to body: this should not happen`);
|
||||
}
|
||||
|
||||
|
||||
// render ourselves; everything is automatically managed from that moment, we just added
|
||||
// a level of indirection but this will not interfere with data
|
||||
const popup = new PopupComponent();
|
||||
Blaze.renderWithData(
|
||||
popup.renderComponent(BlazeComponent.currentComponent()),
|
||||
args,
|
||||
args.openerElement,
|
||||
null,
|
||||
openerView
|
||||
);
|
||||
return popup;
|
||||
}
|
||||
|
||||
static destroy() {
|
||||
PopupComponent.stack.at(-1)?.destroy();
|
||||
}
|
||||
|
||||
static findParentPopup(element) {
|
||||
return BlazeComponent.getComponentForElement($(element).closest('.pop-over')[0]);
|
||||
}
|
||||
|
||||
static toFront(event) {
|
||||
const popup = PopupComponent.findParentPopup(event.target)
|
||||
popup?.toFront();
|
||||
return popup;
|
||||
}
|
||||
|
||||
static toBack(event) {
|
||||
const popup = PopupComponent.findParentPopup(event.target);
|
||||
popup?.toBack();
|
||||
return popup;
|
||||
}
|
||||
|
||||
static maximize(event) {
|
||||
const popup = PopupComponent.findParentPopup(event.target);
|
||||
popup?.toFront();
|
||||
popup?.maximize();
|
||||
return popup;
|
||||
}
|
||||
|
||||
static minimize(event) {
|
||||
const popup = PopupComponent.findParentPopup(event.target);
|
||||
popup?.minimize();
|
||||
return popup;
|
||||
}
|
||||
|
||||
|
||||
getOpenerElement(view) {
|
||||
// Look for the first parent view whose first DOM element is not virtually us
|
||||
const firstNode = $(view.firstNode());
|
||||
|
||||
// The goal is to have the best chances to get the element whose size and pos
|
||||
// are relevant; e.g. when clicking on a date on a minicard, we don't wan't
|
||||
// the opener to be set to the minicard.
|
||||
// In order to work in general, we need to take special situations into account,
|
||||
// e.g. the placeholder is isolated, or does not have previous node, and so on.
|
||||
// In general we prefer previous node, then next, then any displayed sibling,
|
||||
// then the parent, and so on.
|
||||
let candidates = [];
|
||||
if (!firstNode.hasClass(this.popupPlaceholderClass())) {
|
||||
candidates.push(firstNode);
|
||||
}
|
||||
candidates = candidates.concat([firstNode.prev(), firstNode.next()]);
|
||||
const otherSiblings = Array.from(firstNode.siblings()).filter(e => !candidates.includes(e));
|
||||
|
||||
for (const cand of candidates.concat(otherSiblings)) {
|
||||
const displayCSS = cand?.css("display");
|
||||
if (displayCSS && displayCSS !== "none") {
|
||||
return cand[0];
|
||||
}
|
||||
}
|
||||
return this.getOpenerElement(view.parentView);
|
||||
}
|
||||
|
||||
getParentData(view) {;
|
||||
let data;
|
||||
// ⚠️ node can be a text node
|
||||
while (view.firstNode?.()?.classList?.contains(this.popupPlaceholderClass())) {
|
||||
view = view.parentView;
|
||||
data = Blaze.getData(view);
|
||||
}
|
||||
// This is VERY IMPORTANT to get data like this and not with templateInstance.data,
|
||||
// because this form is reactive. So all inner popups have reactive data, which is nice
|
||||
return data;
|
||||
}
|
||||
|
||||
onCreated() {
|
||||
// #FIXME prevent secondary popups to open
|
||||
// Special "magic number" case: never render, for any reason, the same card
|
||||
// const maybeID = this.parentComponent?.()?.data?.()?._id;
|
||||
// if (maybeID && PopupComponent.stack.find(e => e.parentComponent().data?.()?._id === maybeID)) {
|
||||
// this.destroy();
|
||||
// return;
|
||||
// }
|
||||
// do not render a template multiple times
|
||||
const existing = PopupComponent.stack.find((e) => (e.name == this.data().name));
|
||||
if (existing && PopupComponent.multipleBlacklist.indexOf(this.data().name)) {
|
||||
// ⚠️ is there a default better than another? I feel that closing existing
|
||||
// popup is not bad in general because having the same button for open and close
|
||||
// is common
|
||||
if (PopupComponent.multipleBlacklist.includes(existing.name)) {
|
||||
existing.destroy();
|
||||
}
|
||||
// but is could also be re-rendering, eg
|
||||
// existing.render();
|
||||
return;
|
||||
}
|
||||
|
||||
// All of this, except name, is optional. The rest is provided "just in case", for convenience (hopefully)
|
||||
//
|
||||
// - name is the name of a template to render inside the popup (to the detriment of its size) or the contrary
|
||||
// - showHeader can be turned off if the inner content always have a header with buttons and so on
|
||||
// - title is shown when header is shown
|
||||
// - miscOptions is for compatibility
|
||||
// - closeVar is an optional string representing a Session variable: if set, the popup reactively closes when the variable changes and set the variable to null on close
|
||||
// - closeDOMs can be used alternatively; it is an array of "<event> <selector>" to listen that closes the popup.
|
||||
// if header is shown, closing the popup is already managed. selector is relative to the inner template (same as its event map)
|
||||
// - followDOM is an element whose dimension will serve as reference so that popup can react to inner changes; works only with inline styles (otherwise we probably would need IntersectionObserver-like stuff, async etc)
|
||||
// - handleDOM is an element who can be clicked to move popup
|
||||
// it is useful when the content can be redimensionned/moved by code or user; we still manage events, resizes etc
|
||||
// but allow inner elements or handles to do it (and we adapt).
|
||||
const data = this.data();
|
||||
this.popupArgs = {
|
||||
name: data.name,
|
||||
showHeader: data.showHeader ?? true,
|
||||
title: data.title,
|
||||
openerElement: data.openerElement,
|
||||
closeDOMs: data.closeDOMs,
|
||||
followDOM: data.followDOM,
|
||||
handleDOM: data.handleDOM,
|
||||
forceData: data.miscOptions?.dataContextIfCurrentDataIsUndefined,
|
||||
afterConfirm: data.miscOptions?.afterConfirm,
|
||||
}
|
||||
this.name = this.data().name;
|
||||
|
||||
this.innerTemplate = Template[this.name];
|
||||
this.innerComponent = BlazeComponent.getComponent(this.name);
|
||||
|
||||
this.outerComponent = BlazeComponent.getComponent('popupDetached');
|
||||
if (!(this.innerComponent || this.innerTemplate)) {
|
||||
throw new Error(`template and/or component ${this.name} not found`);
|
||||
}
|
||||
|
||||
// If arg is not set, must be closed manually by calling destroy()
|
||||
if (this.popupArgs.closeVar) {
|
||||
this.closeInitialValue = Session.get(this.data().closeVar);
|
||||
if (!this.closeInitialValue === undefined) {
|
||||
this.autorun(() => {
|
||||
if (Session.get(this.data().closeVar) !== this.closeInitialValue) {
|
||||
this.onDestroyed();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popupPlaceholderClass() {
|
||||
return "popup-placeholder";
|
||||
}
|
||||
|
||||
render() {
|
||||
const oldOuterView = this.outerView;
|
||||
// see below for comments
|
||||
this.outerView = Blaze.renderWithData(
|
||||
// data is passed through the parent relationship
|
||||
// we need to render it again to keep events in sync with inner popup
|
||||
this.outerComponent.renderComponent(this.component()),
|
||||
this.popupArgs,
|
||||
document.body,
|
||||
null,
|
||||
this.openerView
|
||||
);
|
||||
this.innerView = Blaze.renderWithData(
|
||||
// the template to render: either the content is a BlazeComponent or a regular template
|
||||
// if a BlazeComponent, render it as a template first
|
||||
this.innerComponent?.renderComponent?.(this.component()) || this.innerTemplate,
|
||||
// dataContext used for rendering: each time we go find data, because it is non-reactive
|
||||
() => (this.popupArgs.forceData || this.getParentData(this.currentView)),
|
||||
// DOM parent: ask to the detached popup, will be inserted at the last child
|
||||
this.outerView.firstNode()?.getElementsByClassName('content')?.[0] || document.body,
|
||||
// "stop" DOM element; we don't use
|
||||
null,
|
||||
// important: this is the Blaze.View object which will be set as `parentView` of
|
||||
// the rendered view. we set it as the parent view, so that the detached popup
|
||||
// can interact with its "parent" without being a child of it, and without
|
||||
// manipulating DOM directly.
|
||||
this.openerView
|
||||
);
|
||||
if (oldOuterView) {
|
||||
Blaze.remove(oldOuterView);
|
||||
}
|
||||
}
|
||||
|
||||
onRendered() {
|
||||
if (this.detached) {return}
|
||||
// Use plain Blaze stuff to be able to render all templates, but use components when available/relevant
|
||||
this.currentView = Blaze.currentView || Blaze.getView(this.component().firstNode());
|
||||
|
||||
// Placement will be related to the opener (usually clicked element)
|
||||
// But template data and view related to the opener are not the same:
|
||||
// - view is probably outer, as is was already rendered on click
|
||||
// - template data could be found with Template.parentData(n), but `n` can
|
||||
// vary depending on context: using those methods feels more reliable for this use case
|
||||
this.popupArgs.openerElement ??= this.getOpenerElement(this.currentView);
|
||||
this.openerView = Blaze.getView(this.popupArgs.openerElement);
|
||||
// With programmatic/click opening, we get the "real" opener; with dynamic
|
||||
// templating we get the placeholder and need to go up to get a glimpse of
|
||||
// the "real" opener size. It is quite imprecise in that case (maybe the
|
||||
// interesting opener is a sibling, not an ancestor), but seems to do the job
|
||||
// for now.
|
||||
// Also it feels sane that inner content does not have a reference to
|
||||
// a virtual placeholder.
|
||||
const opener = this.popupArgs.openerElement;
|
||||
let sizedOpener = opener;
|
||||
if (opener.classList?.contains?.(this.popupPlaceholderClass())) {
|
||||
sizedOpener = opener.parentNode;
|
||||
}
|
||||
this.popupArgs.nonPlaceholderOpener = sizedOpener;
|
||||
|
||||
PopupComponent.stack.push(this);
|
||||
|
||||
try {
|
||||
this.render();
|
||||
// Render above other popups by default
|
||||
} catch(e) {
|
||||
// If something went wrong during rendering, do not create
|
||||
// "zombie" popups
|
||||
console.error(`cannot render popup ${this.name}: ${e}`);
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.detached = true;
|
||||
if (!PopupComponent.stack.includes(this)) {
|
||||
// Avoid loop destroy
|
||||
return;
|
||||
}
|
||||
// Maybe overkill but may help to avoid leaking memory
|
||||
// as programmatic rendering is less usual
|
||||
for (const view of [this.innerView, this.currentView, this.outerView]) {
|
||||
try {
|
||||
Blaze.remove(view);
|
||||
} catch {
|
||||
console.warn(`A view failed to be removed: ${view}`)
|
||||
}
|
||||
}
|
||||
this.innerComponent?.removeComponent?.();
|
||||
this.outerComponent?.removeComponent?.();
|
||||
this.removeComponent();
|
||||
|
||||
// not necesserly removed in order, e.g. multiple cards
|
||||
PopupComponent.stack = PopupComponent.stack.filter(e => e !== this);
|
||||
}
|
||||
|
||||
|
||||
closeWithPlaceholder(parentElement) {
|
||||
// adapted from https://stackoverflow.com/questions/52834774/dom-event-when-element-is-removed
|
||||
// strangely, when opener is removed because of a reactive change, this component
|
||||
// do not get any lifecycle hook called, so we need to bridge the gap. Simply
|
||||
// "close" popup when placeholder is off-DOM.
|
||||
while (parentElement.nodeType === Node.TEXT_NODE) {
|
||||
parentElement = parentElement.parentElement;
|
||||
}
|
||||
const placeholder = parentElement.getElementsByClassName(this.popupPlaceholderClass());
|
||||
if (!placeholder.length) {
|
||||
return;
|
||||
}
|
||||
const observer = new MutationObserver(() => {
|
||||
// DOM element being suppressed is reflected in array
|
||||
if (placeholder.length === 0) {
|
||||
this.destroy();
|
||||
}
|
||||
});
|
||||
observer.observe(parentElement, {childList: true});
|
||||
}
|
||||
}
|
||||
|
||||
PopupComponent.register("popup");
|
||||
PopupDetachedComponent.register('popupDetached');
|
||||
|
||||
export default PopupComponent;
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
.pop-over.js-pop-over(
|
||||
class="{{#unless title}}miniprofile{{/unless}}"
|
||||
class=currentBoard.colorClass
|
||||
class="{{#unless title}}no-title{{/unless}}"
|
||||
data-popup="{{popupName}}"
|
||||
style="left:{{offset.left}}px; top:{{offset.top}}px;{{#if offset.maxHeight}} max-height:{{offset.maxHeight}}px;{{/if}}")
|
||||
.header
|
||||
a.back-btn.js-back-view(class="{{#unless hasPopupParent}}is-hidden{{/unless}}")
|
||||
i.fa.fa-caret-left
|
||||
span.header-title= title
|
||||
a.close-btn.js-close-pop-over
|
||||
i.fa.fa-times-thin
|
||||
.content-wrapper
|
||||
//-
|
||||
We display the all stack of popup content next to each other and move
|
||||
the "window" by translating .content-container inside .content-wrapper.
|
||||
.content-container(class="popup-container-depth-{{depth}}")
|
||||
each stack
|
||||
//-
|
||||
XXX We need a better way to express the "is the last element" condition.
|
||||
Hopefully the @last helper will come soon (or at least @index)
|
||||
.content(class="{{#unless $eq popupName ../popupName}}no-height{{/unless}}")
|
||||
+Template.dynamic(template=popupName data=dataContext)
|
||||
.clearfix
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
height: 50px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
|
||||
}
|
||||
.sk-spinner-wave div {
|
||||
background-color: #333;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,40 @@
|
|||
#notifications {
|
||||
position: relative;
|
||||
}
|
||||
#notifications .notifications-drawer-toggle {
|
||||
display: block;
|
||||
line-height: 28px;
|
||||
color: #f2f2f2;
|
||||
margin: 0 10px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
text-align: center;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
.notifications-container {
|
||||
/* absolute to render close to emoji and render on top,
|
||||
"naturally" on top because no parent stacking context */
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 1.5lh;
|
||||
background-color: #fafafa;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 2px;
|
||||
color: #000;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#notifications .notifications-drawer-toggle.alert {
|
||||
background-color: #eb4646;
|
||||
}
|
||||
|
||||
#notifications {
|
||||
/* to position popup */
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#notifications-drawer {
|
||||
position: relative;
|
||||
min-height: min-content;
|
||||
height: fit-content;
|
||||
max-height: 100vh;
|
||||
z-index: 300;
|
||||
width: max-content;
|
||||
.fa {
|
||||
color: #bcbcbc !important;
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile-mode {
|
||||
#notifications-drawer .header {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
template(name='notifications')
|
||||
#notifications.board-header-btns.right
|
||||
.notifications-container
|
||||
if $.Session.get 'showNotificationsDrawer'
|
||||
+notificationsDrawer(unreadNotifications=unreadNotifications)
|
||||
a.notifications-drawer-toggle(class="{{#if $gt unreadNotifications 0}}alert{{/if}}" title="{{_ 'notifications'}}")
|
||||
i.fa.fa-bell
|
||||
if $.Session.get 'showNotificationsDrawer'
|
||||
+notificationsDrawer(unreadNotifications=unreadNotifications)
|
||||
|
|
|
|||
|
|
@ -1,38 +1,16 @@
|
|||
section#notifications-drawer {
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
right: 0;
|
||||
width: 400px;
|
||||
background-color: #fafafa;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
||||
border-radius: 2px;
|
||||
max-height: calc(100vh - 28px - 36px);
|
||||
color: #000;
|
||||
padding-top: 36px;
|
||||
}
|
||||
section#notifications-drawer a:hover {
|
||||
color: #2980b9 !important;
|
||||
}
|
||||
section#notifications-drawer .header {
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
right: 0;
|
||||
width: calc(400px - 32px);
|
||||
padding: 8px 16px;
|
||||
section#notifications-drawer .header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5lh 2ch;
|
||||
gap: 0.5lh;
|
||||
align-items: center;
|
||||
background: #ededed;
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
z-index: 2;
|
||||
}
|
||||
section#notifications-drawer .header .notification-menu-toggle {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: calc(50% - 12px);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
color: #333;
|
||||
line-height: 24px;
|
||||
}
|
||||
section#notifications-drawer .header .notification-menu-toggle:hover {
|
||||
section#notifications-drawer .header .toggle-read {
|
||||
color: #2980b9;
|
||||
}
|
||||
section#notifications-drawer .header .notification-menu {
|
||||
|
|
@ -88,19 +66,13 @@ section#notifications-drawer .header h5 {
|
|||
margin: 0;
|
||||
}
|
||||
section#notifications-drawer .header .close {
|
||||
position: absolute;
|
||||
top: calc(50% - 12px);
|
||||
right: 12px;
|
||||
font-size: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
section#notifications-drawer ul.notifications {
|
||||
display: block;
|
||||
padding: 0px 16px 0px 16px;
|
||||
margin: 0;
|
||||
height: calc(100vh - 122px);
|
||||
overflow-y: scroll;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ template(name='notificationsDrawer')
|
|||
.header
|
||||
a.notification-menu-toggle
|
||||
i.fa.fa-bars
|
||||
//- #FIXME could be replaced by a popup to help placement ?
|
||||
.notification-menu(class="{{#if $.Session.get 'showNotificationMenu'}}is-open{{/if}}")
|
||||
.menu-section
|
||||
a.menu-item(class="{{#unless $.Session.get 'showReadNotifications'}}selected{{/unless}}")
|
||||
|
|
@ -44,9 +45,10 @@ template(name='notificationsDrawer')
|
|||
span.menu-icon
|
||||
i.fa.fa-trash
|
||||
span {{_ 'delete-all-notifications'}}
|
||||
h5 {{_ 'notifications'}}
|
||||
if($gt unreadNotifications 0)
|
||||
|(#{unreadNotifications})
|
||||
if($gt unreadNotifications 0)
|
||||
|(#{unreadNotifications}) {{_ 'notifications'}}
|
||||
else
|
||||
|0 {{_ 'notifications'}}
|
||||
a.close
|
||||
i.fa.fa-times-thin
|
||||
ul.notifications
|
||||
|
|
|
|||
|
|
@ -85,4 +85,5 @@ template(name="setCardActionsColorPopup")
|
|||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
.form-buttons
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
.triggers-content .triggers-body .triggers-side-menu {
|
||||
background-color: #f7f7f7;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
height: intrinsic;
|
||||
box-shadow: inset -1px -1px 3px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
width: 50px;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
.triggers-content .triggers-body .triggers-side-menu ul li i {
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
width: 95%;
|
||||
}
|
||||
.triggers-content .triggers-body .triggers-side-menu ul li a span {
|
||||
font-size: 13px;
|
||||
|
||||
}
|
||||
.triggers-content .triggers-body .triggers-main-body {
|
||||
padding: 0.1em 1em;
|
||||
|
|
@ -134,15 +134,15 @@
|
|||
left: 10px;
|
||||
}
|
||||
.triggers-content .triggers-body .triggers-main-body .trigger-item .trigger-content .trigger-text {
|
||||
font-size: 16px;
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
.triggers-content .triggers-body .triggers-main-body .trigger-item .trigger-content .trigger-inline-button {
|
||||
font-size: 16px;
|
||||
|
||||
display: inline;
|
||||
padding: 6px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
box-shadow: inset -1px -1px 3px rgba(0,0,0,0.05);
|
||||
}
|
||||
.triggers-content .triggers-body .triggers-main-body .trigger-item .trigger-content .trigger-inline-button:hover,
|
||||
|
|
@ -179,10 +179,10 @@
|
|||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
box-shadow: inset -1px -1px 3px rgba(0,0,0,0.05);
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
|
||||
right: 10px;
|
||||
}
|
||||
.triggers-content .triggers-body .triggers-main-body .trigger-item .trigger-button i {
|
||||
|
|
@ -206,7 +206,7 @@
|
|||
top: unset;
|
||||
position: unset;
|
||||
transform: unset;
|
||||
font-size: 16px;
|
||||
|
||||
width: auto;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
.migration-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
@ -35,8 +35,8 @@
|
|||
|
||||
.migration-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
|
||||
border-radius: 0.4ch;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
.migration-progress {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
|
@ -128,20 +128,20 @@
|
|||
text-align: center;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
font-size: 18px;
|
||||
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.current-step {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
.migration-status {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
|
||||
background-color: #e3f2fd;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
|
|
@ -173,7 +173,7 @@
|
|||
.migration-steps h3 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
}
|
||||
|
||||
.migration-step {
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(102, 126, 234, 0);
|
||||
box-shadow: 0 0 0 0.5rem rgba(102, 126, 234, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
|
||||
|
|
@ -225,7 +225,7 @@
|
|||
|
||||
.step-icon {
|
||||
margin-right: 12px;
|
||||
font-size: 18px;
|
||||
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -249,13 +249,13 @@
|
|||
.step-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +265,7 @@
|
|||
}
|
||||
|
||||
.step-progress .progress-text {
|
||||
font-size: 12px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +302,7 @@
|
|||
.jobs-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
@ -313,8 +313,8 @@
|
|||
|
||||
.jobs-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
|
||||
border-radius: 0.4ch;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
|
@ -337,7 +337,7 @@
|
|||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
|
@ -356,18 +356,18 @@
|
|||
.table th {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.table td {
|
||||
font-size: 14px;
|
||||
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
border-radius: 0.4ch;
|
||||
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
|
@ -404,7 +404,7 @@
|
|||
|
||||
.btn-group .btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
|
@ -452,7 +452,7 @@
|
|||
.add-job-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
@ -474,15 +474,15 @@
|
|||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
border-radius: 0.4ch;
|
||||
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
|
|
@ -504,8 +504,8 @@
|
|||
|
||||
.form-actions .btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
|
||||
border-radius: 0.4ch;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
|
@ -546,7 +546,7 @@
|
|||
.board-operations-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
@ -562,8 +562,8 @@
|
|||
|
||||
.board-operations-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
|
||||
border-radius: 0.4ch;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
|
@ -590,7 +590,7 @@
|
|||
.board-operations-stats {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
|
@ -606,14 +606,14 @@
|
|||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
|
@ -622,7 +622,7 @@
|
|||
.system-resources {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
|
@ -641,7 +641,7 @@
|
|||
min-width: 120px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.resource-bar {
|
||||
|
|
@ -674,7 +674,7 @@
|
|||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.board-operations-search {
|
||||
|
|
@ -683,7 +683,7 @@
|
|||
|
||||
.search-box {
|
||||
position: relative;
|
||||
max-width: 400px;
|
||||
max-width: 50vw;
|
||||
}
|
||||
|
||||
.search-box .form-control {
|
||||
|
|
@ -696,12 +696,12 @@
|
|||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
|
||||
}
|
||||
|
||||
.board-operations-list {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -718,13 +718,13 @@
|
|||
.operations-header h3 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.operations-table {
|
||||
|
|
@ -751,11 +751,11 @@
|
|||
|
||||
.board-id {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
|
||||
color: #666;
|
||||
background: #f8f9fa;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
|
@ -776,19 +776,19 @@
|
|||
flex: 1;
|
||||
height: 8px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-container .progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-container .progress-text {
|
||||
font-size: 12px;
|
||||
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
min-width: 35px;
|
||||
|
|
@ -806,8 +806,8 @@
|
|||
|
||||
.pagination .btn {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
border-radius: 0.4ch;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #333;
|
||||
|
|
@ -827,7 +827,7 @@
|
|||
|
||||
.page-info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
|
|
@ -838,26 +838,26 @@
|
|||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
|
||||
.migration-controls,
|
||||
.jobs-controls {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.table {
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
|
||||
.btn-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.add-job-form {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
@ -878,7 +878,7 @@
|
|||
#cron-setting .progress {
|
||||
height: 30px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
overflow: visible;
|
||||
margin-bottom: 5px;
|
||||
max-width: calc(100% - 40px);
|
||||
|
|
@ -893,7 +893,7 @@
|
|||
font-size: 14px;
|
||||
text-align: center;
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
}
|
||||
|
||||
#cron-setting .progress-text {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
.migration-progress-modal {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
|
|
@ -46,13 +46,13 @@
|
|||
|
||||
.migration-progress-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.migration-progress-close {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.migration-progress-overall-bar {
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
|
||||
.migration-progress-overall-percentage {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
@ -123,12 +123,12 @@
|
|||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.migration-progress-step-bar {
|
||||
background: #e9ecef;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 5px;
|
||||
|
|
@ -137,13 +137,13 @@
|
|||
.migration-progress-step-fill {
|
||||
background: linear-gradient(90deg, #007bff, #0056b3);
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
border-radius: 0.8ch;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.migration-progress-step-percentage {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
@ -160,12 +160,12 @@
|
|||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
|
||||
}
|
||||
|
||||
.migration-progress-status-text {
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
|
@ -181,12 +181,12 @@
|
|||
font-weight: 600;
|
||||
color: #1976d2;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
|
||||
}
|
||||
|
||||
.migration-progress-details-text {
|
||||
color: #1565c0;
|
||||
font-size: 13px;
|
||||
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +199,7 @@
|
|||
.migration-progress-note {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
|
@ -209,17 +209,17 @@
|
|||
width: 95%;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-header {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-title {
|
||||
font-size: 16px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -229,40 +229,40 @@
|
|||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-overall-label,
|
||||
.migration-progress-step-label,
|
||||
.migration-progress-status-label {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-status {
|
||||
background: #4a5568;
|
||||
border-left-color: #63b3ed;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-status-text {
|
||||
color: #cbd5e0;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-details {
|
||||
background: #2b6cb0;
|
||||
border-left-color: #4299e1;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-details-label {
|
||||
color: #bee3f8;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-details-text {
|
||||
color: #90cdf4;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-footer {
|
||||
background: #4a5568;
|
||||
border-top-color: #718096;
|
||||
}
|
||||
|
||||
|
||||
.migration-progress-note {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
|
@ -285,7 +285,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
}
|
||||
|
||||
.migration-spinner {
|
||||
|
|
|
|||
|
|
@ -39,9 +39,6 @@ table tr:nth-child(even) {
|
|||
.ext-box button {
|
||||
min-width: 90px;
|
||||
}
|
||||
.content-wrapper {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.buttonsContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
|
@ -164,7 +161,7 @@ table td:first-child {
|
|||
background-color: #27ae60;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.4ch;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
animation: fadeOut 3s ease-in forwards;
|
||||
|
|
|
|||
|
|
@ -9,19 +9,32 @@
|
|||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.setting-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: stretch;
|
||||
align-items: stretch;
|
||||
|
||||
}
|
||||
.setting-content {
|
||||
color: #727479;
|
||||
background: #dedede;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.setting-content .wekan-form-control:not([type="radio"]) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.setting-content .content-title {
|
||||
font-size: clamp(16px, 3.5vw, 22px);
|
||||
font-size: 1.3em;
|
||||
padding: 0.5lh 1ch;
|
||||
}
|
||||
.setting-content .content-body {
|
||||
display: flex;
|
||||
padding-top: 2vh;
|
||||
height: 100%;
|
||||
gap: 1.3vw;
|
||||
}
|
||||
|
|
@ -29,8 +42,15 @@
|
|||
background-color: #f7f7f7;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 0.5vw;
|
||||
width: min(250px, 32vw);
|
||||
min-width: fit-content;
|
||||
box-shadow: inset -0.2vh -0.2vh 0.4vh rgba(0,0,0,0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-right: 2ch;
|
||||
overflow-y: scroll;
|
||||
min-height: 20vh;
|
||||
flex-grow: 1;
|
||||
|
||||
}
|
||||
.setting-content .content-body .side-menu ul li {
|
||||
margin: 0.2vh 0.3vw;
|
||||
|
|
@ -47,12 +67,10 @@
|
|||
padding: 1.3vh 0 1.3vh 1.3vw;
|
||||
width: 95%;
|
||||
}
|
||||
.setting-content .content-body .side-menu ul li a span {
|
||||
font-size: 13px;
|
||||
}
|
||||
.setting-content .content-body .side-menu ul li a i {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.setting-content .content-body .main-body {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
|
|
@ -62,9 +80,9 @@
|
|||
overflow-x: scroll !important;
|
||||
overflow-y: scroll !important;
|
||||
scrollbar-gutter: stable;
|
||||
/* Force horizontal scrollbar to always be visible */
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
flex-grow: 5;
|
||||
padding-right: 2ch;
|
||||
padding-bottom: 1lh;
|
||||
}
|
||||
|
||||
/* Ensure scrollbars are always visible with proper styling for all admin pages */
|
||||
|
|
@ -126,7 +144,6 @@
|
|||
.setting-content .content-body .main-body::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
|
@ -137,7 +154,7 @@
|
|||
padding: 0.5rem 0.5rem;
|
||||
}
|
||||
.setting-content .content-body .main-body ul li a .is-checked {
|
||||
border-bottom: 2px solid #3cb500;
|
||||
border-bottom: 0.2ch solid #3cb500;
|
||||
border-right: 2px solid #3cb500;
|
||||
}
|
||||
/* Grey checkmarks when grey icons setting is enabled */
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
margin-left: 20px;
|
||||
padding-right: 10px;
|
||||
height: 28px;
|
||||
font-size: 13px;
|
||||
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
line-height: 28px;
|
||||
|
|
@ -26,3 +26,12 @@
|
|||
margin-top: 1px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
.setting-header-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1ch;
|
||||
padding: 0 1ch;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
|
@ -32,9 +32,6 @@ table tr:nth-child(even) {
|
|||
.ext-box button {
|
||||
min-width: 90px;
|
||||
}
|
||||
.content-wrapper {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.buttonsContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
font-size: 25px;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
.sidebar-xmark:hover {
|
||||
|
|
@ -27,7 +27,21 @@
|
|||
padding: 10px 10px 0px 10px;
|
||||
}
|
||||
.sidebar .sidebar-content {
|
||||
padding: 0 12px;
|
||||
padding: 0 1ch;
|
||||
>ul {
|
||||
display: flex;
|
||||
}
|
||||
.fa:not(.fa-plus) {
|
||||
padding-right: 0.5ch;
|
||||
align-self: center;
|
||||
}
|
||||
*:has(>.fa-plus) {
|
||||
/* as long as container as a min height,
|
||||
we can accomodate it while staying symetric */
|
||||
aspect-ratio: 1/1;
|
||||
height: var(--label-height);
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
.sidebar .sidebar-content .hide-btn {
|
||||
display: none;
|
||||
|
|
@ -38,15 +52,13 @@
|
|||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.sidebar .sidebar-content h3 i.fa {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.sidebar .sidebar-content hr {
|
||||
margin: 13px 0;
|
||||
}
|
||||
.sidebar .sidebar-content ul.sidebar-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1lh;
|
||||
}
|
||||
|
||||
/* Use checklist-style green checkboxes for all sidebar checkboxes */
|
||||
|
|
@ -60,7 +72,7 @@
|
|||
margin-right: 6px !important;
|
||||
border-top: 2px solid transparent !important;
|
||||
border-left: 2px solid transparent !important;
|
||||
border-bottom: 2px solid #3cb500 !important;
|
||||
border-bottom: 0.2ch solid #3cb500 !important;
|
||||
border-right: 2px solid #3cb500 !important;
|
||||
transform: rotate(40deg) !important;
|
||||
-webkit-backface-visibility: hidden !important;
|
||||
|
|
@ -105,16 +117,17 @@ body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked
|
|||
|
||||
.card-settings-column h4 {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
.sidebar .sidebar-content ul.sidebar-list li > a {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
max-height: 2lh;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
}
|
||||
.sidebar .sidebar-content ul.sidebar-list li > a:hover,
|
||||
|
|
@ -132,10 +145,6 @@ body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked
|
|||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.sidebar .sidebar-content ul.sidebar-list li > a .sidebar-list-item-description {
|
||||
flex: 1;
|
||||
overflow: ellipsis;
|
||||
}
|
||||
.sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-check {
|
||||
margin: 0 4px;
|
||||
color: #3cb500;
|
||||
|
|
@ -144,9 +153,6 @@ body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked
|
|||
body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-check {
|
||||
color: #7a7a7a;
|
||||
}
|
||||
.sidebar .sidebar-content ul.sidebar-list li .minicard {
|
||||
padding: 6px 8px 4px;
|
||||
}
|
||||
.sidebar .sidebar-content ul.sidebar-list li .minicard .minicard-edit-button {
|
||||
float: right;
|
||||
padding: 4px;
|
||||
|
|
@ -183,13 +189,28 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
|
|||
}
|
||||
.board-sidebar {
|
||||
display: none;
|
||||
width: 30vw;
|
||||
z-index: 100;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
max-width: min(50ch, 50vw);
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
z-index: 10;
|
||||
transition: top 0.1s, right 0.1s, width 0.1s;
|
||||
}
|
||||
|
||||
body.mobile-mode .board-sidebar {
|
||||
max-width: 100vw;
|
||||
}
|
||||
.board-sidebar.is-open {
|
||||
display: block;
|
||||
}
|
||||
.board-widget-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.2lh;
|
||||
min-height: 1.5lh;
|
||||
align-items: stretch;
|
||||
}
|
||||
.board-widget h4 {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
|
@ -212,7 +233,7 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
|
|||
}
|
||||
.sidebar-tongue i.fa {
|
||||
padding: 3px 9px;
|
||||
font-size: 24px;
|
||||
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
.sidebar-accessibility {
|
||||
|
|
@ -283,7 +304,7 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
|
|||
}
|
||||
.board-sidebar .sidebar-content .hide-btn i.fa {
|
||||
padding: 8px 16px;
|
||||
font-size: 24px;
|
||||
|
||||
font-weight: bold;
|
||||
}
|
||||
.sidebar-tongue {
|
||||
|
|
|
|||
|
|
@ -48,8 +48,9 @@ template(name='homeSidebar')
|
|||
hr
|
||||
if currentUser.isBoardAdmin
|
||||
h3.activity-title
|
||||
i.fa.fa-comment-o
|
||||
| {{_ 'activities'}}
|
||||
span
|
||||
i.fa.fa-comment-o
|
||||
span {{_ 'activities'}}
|
||||
|
||||
a.flex.js-toggle-show-activities(title="{{_ 'show-activities'}}")
|
||||
i.fa(class="{{#if showActivities}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
|
|
@ -60,7 +61,7 @@ template(name="membersWidget")
|
|||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isWorker
|
||||
h3
|
||||
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
|
||||
a.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
|
||||
i.fa.fa-cog
|
||||
| {{_ 'boardMenuPopup-title'}}
|
||||
hr
|
||||
|
|
@ -161,7 +162,7 @@ template(name="boardChangeBackgroundImagePopup")
|
|||
form
|
||||
label
|
||||
| {{_ 'board-background-image-url'}}
|
||||
input.js-board-background-image-url(type="text" value="{{backgroundImageURL}}" autofocus)
|
||||
input.js-board-background-image-url(type="text" value="{{backgroundImageURL}}" )
|
||||
div.buttonsContainer
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
br
|
||||
|
|
@ -307,7 +308,7 @@ template(name="boardCardSettingsPopup")
|
|||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-user
|
||||
| ➕
|
||||
i.fa.fa-plus
|
||||
| {{_ 'requested-by'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
|
|
@ -635,6 +636,10 @@ template(name="boardMenuPopup")
|
|||
a.js-archive-board
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archive-board'}}
|
||||
//- this popup is the only one to not open
|
||||
//- with correct size; related to issue linked above ?
|
||||
//- artificially add a bit a space
|
||||
div.invisible-line
|
||||
|
||||
template(name="exportBoard")
|
||||
ul.pop-over-list
|
||||
|
|
|
|||
|
|
@ -41,16 +41,15 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
open() {
|
||||
if (!this._isOpen.get()) {
|
||||
this._isOpen.set(true);
|
||||
EscapeActions.executeUpTo('detailsPane');
|
||||
}
|
||||
// setting a ReactiveVar is idempotent;
|
||||
// do not try to get(), because it will
|
||||
// react to changes...
|
||||
this._isOpen.set(true);
|
||||
EscapeActions.executeUpTo('detailsPane');
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (this._isOpen.get()) {
|
||||
this._isOpen.set(false);
|
||||
}
|
||||
this._isOpen.set(false);
|
||||
},
|
||||
|
||||
toggle() {
|
||||
|
|
@ -154,7 +153,7 @@ BlazeComponent.extendComponent({
|
|||
ReactiveCache.getCurrentUser().toggleVerticalScrollbars();
|
||||
},
|
||||
'click .js-show-week-of-year-toggle'() {
|
||||
ReactiveCache.getCurrentUser().toggleShowWeekOfYear();
|
||||
Meteor.call('toggleShowWeekOfYear');
|
||||
},
|
||||
'click .sidebar-accessibility'() {
|
||||
FlowRouter.go('accessibility');
|
||||
|
|
@ -291,10 +290,10 @@ Template.boardMenuPopup.events({
|
|||
'click .js-delete-duplicate-lists': Popup.afterConfirm('deleteDuplicateLists', function() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (!currentBoard) return;
|
||||
|
||||
|
||||
// Get all lists in the current board
|
||||
const allLists = ReactiveCache.getLists({ boardId: currentBoard._id, archived: false });
|
||||
|
||||
|
||||
// Group lists by title to find duplicates
|
||||
const listsByTitle = {};
|
||||
allLists.forEach(list => {
|
||||
|
|
@ -303,7 +302,7 @@ Template.boardMenuPopup.events({
|
|||
}
|
||||
listsByTitle[list.title].push(list);
|
||||
});
|
||||
|
||||
|
||||
// Find and delete duplicate lists that have no cards
|
||||
let deletedCount = 0;
|
||||
Object.keys(listsByTitle).forEach(title => {
|
||||
|
|
@ -313,7 +312,7 @@ Template.boardMenuPopup.events({
|
|||
for (let i = 1; i < listsWithSameTitle.length; i++) {
|
||||
const list = listsWithSameTitle[i];
|
||||
const cardsInList = ReactiveCache.getCards({ listId: list._id, archived: false });
|
||||
|
||||
|
||||
if (cardsInList.length === 0) {
|
||||
Lists.remove(list._id);
|
||||
deletedCount++;
|
||||
|
|
@ -321,7 +320,7 @@ Template.boardMenuPopup.events({
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Show notification
|
||||
if (deletedCount > 0) {
|
||||
// You could add a toast notification here if available
|
||||
|
|
@ -402,7 +401,7 @@ Template.memberPopup.events({
|
|||
FlowRouter.go('home');
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
});
|
||||
|
||||
Template.removeMemberPopup.helpers({
|
||||
|
|
@ -934,7 +933,7 @@ BlazeComponent.extendComponent({
|
|||
// Get the current board reactively using board ID from Session
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
|
||||
|
||||
let result = currentBoard ? currentBoard.presentParentTask : null;
|
||||
if (result === null || result === undefined) {
|
||||
result = 'no-parent';
|
||||
|
|
@ -947,7 +946,7 @@ BlazeComponent.extendComponent({
|
|||
{
|
||||
'click .js-field-has-subtasks'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsSubtasks;
|
||||
const newValue = !this.allowsSubtasks();
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsSubtasks: newValue } });
|
||||
$('.js-field-deposit-board').prop(
|
||||
'disabled',
|
||||
|
|
@ -970,7 +969,7 @@ BlazeComponent.extendComponent({
|
|||
// Get the ID from the anchor element, not the span
|
||||
const anchorElement = $(evt.target).closest('.js-field-show-parent-in-minicard')[0];
|
||||
const value = anchorElement ? anchorElement.id : null;
|
||||
|
||||
|
||||
if (value) {
|
||||
Boards.update(this.currentBoard._id, { $set: { presentParentTask: value } });
|
||||
}
|
||||
|
|
@ -986,171 +985,6 @@ BlazeComponent.extendComponent({
|
|||
this.currentBoard = Utils.getCurrentBoard();
|
||||
},
|
||||
|
||||
allowsReceivedDate() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsReceivedDate : false;
|
||||
},
|
||||
|
||||
allowsStartDate() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsStartDate : false;
|
||||
},
|
||||
|
||||
allowsDueDate() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsDueDate : false;
|
||||
},
|
||||
|
||||
allowsEndDate() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsEndDate : false;
|
||||
},
|
||||
|
||||
allowsSubtasks() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsSubtasks : false;
|
||||
},
|
||||
|
||||
allowsCreator() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? (currentBoard.allowsCreator ?? false) : false;
|
||||
},
|
||||
|
||||
allowsCreatorOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? (currentBoard.allowsCreatorOnMinicard ?? false) : false;
|
||||
},
|
||||
|
||||
allowsMembers() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsMembers : false;
|
||||
},
|
||||
|
||||
allowsAssignee() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsAssignee : false;
|
||||
},
|
||||
|
||||
allowsAssignedBy() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsAssignedBy : false;
|
||||
},
|
||||
|
||||
allowsRequestedBy() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsRequestedBy : false;
|
||||
},
|
||||
|
||||
allowsCardSortingByNumber() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsCardSortingByNumber : false;
|
||||
},
|
||||
|
||||
allowsShowLists() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsShowLists : false;
|
||||
},
|
||||
|
||||
allowsLabels() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsLabels : false;
|
||||
},
|
||||
|
||||
allowsShowListsOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsShowListsOnMinicard : false;
|
||||
},
|
||||
|
||||
allowsChecklists() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsChecklists : false;
|
||||
},
|
||||
|
||||
allowsAttachments() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsAttachments : false;
|
||||
},
|
||||
|
||||
allowsComments() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsComments : false;
|
||||
},
|
||||
|
||||
allowsCardNumber() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsCardNumber : false;
|
||||
},
|
||||
|
||||
allowsDescriptionTitle() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsDescriptionTitle : false;
|
||||
},
|
||||
|
||||
allowsDescriptionText() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsDescriptionText : false;
|
||||
},
|
||||
|
||||
isBoardSelected() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.dateSettingsDefaultBoardID : false;
|
||||
},
|
||||
|
||||
isNullBoardSelected() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? (
|
||||
currentBoard.dateSettingsDefaultBoardId === null ||
|
||||
currentBoard.dateSettingsDefaultBoardId === undefined
|
||||
) : true;
|
||||
},
|
||||
|
||||
allowsDescriptionTextOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsDescriptionTextOnMinicard : false;
|
||||
},
|
||||
|
||||
allowsCoverAttachmentOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsCoverAttachmentOnMinicard : false;
|
||||
},
|
||||
|
||||
allowsBadgeAttachmentOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsBadgeAttachmentOnMinicard : false;
|
||||
},
|
||||
|
||||
allowsCardSortingByNumberOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsCardSortingByNumberOnMinicard : false;
|
||||
},
|
||||
|
||||
boards() {
|
||||
const ret = ReactiveCache.getBoards(
|
||||
{
|
||||
|
|
@ -1191,261 +1025,228 @@ BlazeComponent.extendComponent({
|
|||
{
|
||||
'click .js-field-has-receiveddate'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsReceivedDate;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsReceivedDate: newValue } });
|
||||
const newValue = !Utils.allowsReceivedDate();
|
||||
this.currentBoard.setAllowsReceivedDate(newValue);
|
||||
},
|
||||
'click .js-field-has-startdate'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsStartDate;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsStartDate: newValue } });
|
||||
const newValue = !Utils.allowsStartDate();
|
||||
this.currentBoard.setAllowsStartDate(newValue);
|
||||
},
|
||||
'click .js-field-has-enddate'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsEndDate;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsEndDate: newValue } });
|
||||
const newValue = !Utils.allowsEndDate();
|
||||
this.currentBoard.setAllowsEndDate(newValue);
|
||||
},
|
||||
'click .js-field-has-duedate'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsDueDate;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsDueDate: newValue } });
|
||||
const newValue = !Utils.allowsDueDate();
|
||||
this.currentBoard.setAllowsDueDate(newValue);
|
||||
},
|
||||
'click .js-field-has-subtasks'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsSubtasks;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsSubtasks: newValue } });
|
||||
const newValue = !Utils.allowsSubtasks();
|
||||
this.currentBoard.setAllowsSubtasks(newValue);
|
||||
},
|
||||
'click .js-field-has-creator'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsCreator;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsCreator: newValue } });
|
||||
const newValue = !Utils.allowsCreator();
|
||||
this.currentBoard.setAllowsCreator(newValue);
|
||||
},
|
||||
'click .js-field-has-creator-on-minicard'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsCreatorOnMinicard;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsCreatorOnMinicard: newValue } });
|
||||
const newValue = !Utils.allowsCreatorOnMinicard();
|
||||
this.currentBoard.setAllowsCreatorOnMinicard(newValue);
|
||||
},
|
||||
'click .js-field-has-members'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsMembers;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsMembers: newValue } });
|
||||
const newValue = !Utils.allowsMembers();
|
||||
this.currentBoard.setAllowsMembers(newValue);
|
||||
},
|
||||
'click .js-field-has-assignee'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsAssignee;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsAssignee: newValue } });
|
||||
const newValue = !Utils.allowsAssignee();
|
||||
this.currentBoard.setAllowsAssignee(newValue);
|
||||
},
|
||||
'click .js-field-has-assigned-by'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsAssignedBy;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsAssignedBy: newValue } });
|
||||
const newValue = !Utils.allowsAssignedBy();
|
||||
this.currentBoard.setAllowsAssignedBy(newValue);
|
||||
},
|
||||
'click .js-field-has-requested-by'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsRequestedBy;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsRequestedBy: newValue } });
|
||||
const newValue = !Utils.allowsRequestedBy();
|
||||
this.currentBoard.setAllowsRequestedBy(newValue);
|
||||
},
|
||||
'click .js-field-has-card-sorting-by-number'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsCardSortingByNumber;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsCardSortingByNumber: newValue } });
|
||||
const newValue = !Utils.allowsCardSortingByNumber();
|
||||
this.currentBoard.setAllowsCardSortingByNumber(newValue);
|
||||
},
|
||||
'click .js-field-has-card-show-lists'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsShowLists;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsShowLists: newValue } });
|
||||
const newValue = !Utils.allowsShowLists();
|
||||
this.currentBoard.setAllowsShowLists(newValue);
|
||||
},
|
||||
'click .js-field-has-labels'(evt) {
|
||||
evt.preventDefault();
|
||||
const newValue = !this.currentBoard.allowsLabels;
|
||||
Boards.update(this.currentBoard._id, { $set: { allowsLabels: newValue } });
|
||||
const newValue = !Utils.allowsLabels();
|
||||
this.currentBoard.setAllowsLabels(newValue);
|
||||
},
|
||||
'click .js-field-has-card-show-lists-on-minicard'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsShowListsOnMinicard = !this.currentBoard
|
||||
.allowsShowListsOnMinicard;
|
||||
this.currentBoard.setAllowsShowListsOnMinicard(
|
||||
this.currentBoard.allowsShowListsOnMinicard,
|
||||
);
|
||||
const newValue = !Utils.allowsShowListsOnMinicard();
|
||||
this.currentBoard.setAllowsShowListsOnMinicard(newValue);
|
||||
$(`.js-field-has-card-show-lists-on-minicard ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsShowListsOnMinicard,
|
||||
Utils.allowsShowListsOnMinicard(),
|
||||
);
|
||||
$('.js-field-has-card-show-lists-on-minicard').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsShowListsOnMinicard,
|
||||
Utils.allowsShowListsOnMinicard(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-description-title'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsDescriptionTitle = !this.currentBoard
|
||||
.allowsDescriptionTitle;
|
||||
this.currentBoard.setAllowsDescriptionTitle(
|
||||
this.currentBoard.allowsDescriptionTitle,
|
||||
);
|
||||
const newValue = !Utils.allowsDescriptionTitle();
|
||||
this.currentBoard.setAllowsDescriptionTitle(newValue);
|
||||
$(`.js-field-has-description-title ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionTitle,
|
||||
Utils.allowsDescriptionTitle(),
|
||||
);
|
||||
$('.js-field-has-description-title').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionTitle,
|
||||
Utils.allowsDescriptionTitle(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-card-number'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsCardNumber = !this.currentBoard
|
||||
.allowsCardNumber;
|
||||
this.currentBoard.setAllowsCardNumber(
|
||||
this.currentBoard.allowsCardNumber,
|
||||
);
|
||||
const newValue = !Utils.allowsCardNumber();
|
||||
this.currentBoard.setAllowsCardNumber(newValue);
|
||||
$(`.js-field-has-card-number ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCardNumber,
|
||||
Utils.allowsCardNumber(),
|
||||
);
|
||||
$('.js-field-has-card-number').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCardNumber,
|
||||
Utils.allowsCardNumber(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-description-text-on-minicard'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsDescriptionTextOnMinicard = !this.currentBoard
|
||||
.allowsDescriptionTextOnMinicard;
|
||||
this.currentBoard.setallowsDescriptionTextOnMinicard(
|
||||
this.currentBoard.allowsDescriptionTextOnMinicard,
|
||||
);
|
||||
const newValue = !Utils.allowsDescriptionTextOnMinicard();
|
||||
this.currentBoard.setAllowsDescriptionTextOnMinicard(newValue);
|
||||
$(`.js-field-has-description-text-on-minicard ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionTextOnMinicard,
|
||||
Utils.allowsDescriptionTextOnMinicard(),
|
||||
);
|
||||
$('.js-field-has-description-text-on-minicard').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionTextOnMinicard,
|
||||
Utils.allowsDescriptionTextOnMinicard(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-description-text'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsDescriptionText = !this.currentBoard
|
||||
.allowsDescriptionText;
|
||||
this.currentBoard.setAllowsDescriptionText(
|
||||
this.currentBoard.allowsDescriptionText,
|
||||
);
|
||||
const newValue = !Utils.allowsDescriptionText();
|
||||
this.currentBoard.setAllowsDescriptionText(newValue);
|
||||
$(`.js-field-has-description-text ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionText,
|
||||
Utils.allowsDescriptionText(),
|
||||
);
|
||||
$('.js-field-has-description-text').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionText,
|
||||
Utils.allowsDescriptionText(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-checklists'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsChecklists = !this.currentBoard
|
||||
.allowsChecklists;
|
||||
this.currentBoard.setAllowsChecklists(
|
||||
this.currentBoard.allowsChecklists,
|
||||
);
|
||||
const newValue = !Utils.allowsChecklists();
|
||||
this.currentBoard.setAllowsChecklists(newValue);
|
||||
$(`.js-field-has-checklists ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsChecklists,
|
||||
Utils.allowsChecklists(),
|
||||
);
|
||||
$('.js-field-has-checklists').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsChecklists,
|
||||
Utils.allowsChecklists(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-attachments'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsAttachments = !this.currentBoard
|
||||
.allowsAttachments;
|
||||
this.currentBoard.setAllowsAttachments(
|
||||
this.currentBoard.allowsAttachments,
|
||||
);
|
||||
const newValue = !Utils.allowsAttachments();
|
||||
this.currentBoard.setAllowsAttachments(newValue);
|
||||
$(`.js-field-has-attachments ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAttachments,
|
||||
Utils.allowsAttachments(),
|
||||
);
|
||||
$('.js-field-has-attachments').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAttachments,
|
||||
Utils.allowsAttachments(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-comments'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsComments = !this.currentBoard.allowsComments;
|
||||
this.currentBoard.setAllowsComments(this.currentBoard.allowsComments);
|
||||
const newValue = !Utils.allowsComments();
|
||||
this.currentBoard.setAllowsComments(newValue);
|
||||
$(`.js-field-has-comments ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsComments,
|
||||
Utils.allowsComments(),
|
||||
);
|
||||
$('.js-field-has-comments').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsComments,
|
||||
Utils.allowsComments(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-activities'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsActivities = !this.currentBoard
|
||||
.allowsActivities;
|
||||
this.currentBoard.setAllowsActivities(
|
||||
this.currentBoard.allowsActivities,
|
||||
);
|
||||
const newValue = !Utils.allowsActivities();
|
||||
this.currentBoard.setAllowsActivities(newValue);
|
||||
$(`.js-field-has-activities ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsActivities,
|
||||
Utils.allowsActivities(),
|
||||
);
|
||||
$('.js-field-has-activities').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsActivities,
|
||||
Utils.allowsActivities(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-cover-attachment-on-minicard'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsCoverAttachmentOnMinicard = !this.currentBoard
|
||||
.allowsCoverAttachmentOnMinicard;
|
||||
this.currentBoard.setallowsCoverAttachmentOnMinicard(
|
||||
this.currentBoard.allowsCoverAttachmentOnMinicard,
|
||||
);
|
||||
const newValue = !Utils.allowsCoverAttachmentOnMinicard();
|
||||
this.currentBoard.setAllowsCoverAttachmentOnMinicard(newValue);
|
||||
$(`.js-field-has-cover-attachment-on-minicard ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCoverAttachmentOnMinicard,
|
||||
Utils.allowsCoverAttachmentOnMinicard(),
|
||||
);
|
||||
$('.js-field-has-cover-attachment-on-minicard').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCoverAttachmentOnMinicard,
|
||||
Utils.allowsCoverAttachmentOnMinicard(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-badge-attachment-on-minicard'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsBadgeAttachmentOnMinicard = !this.currentBoard
|
||||
.allowsBadgeAttachmentOnMinicard;
|
||||
this.currentBoard.setallowsBadgeAttachmentOnMinicard(
|
||||
this.currentBoard.allowsBadgeAttachmentOnMinicard,
|
||||
);
|
||||
const newValue = !Utils.allowsBadgeAttachmentOnMinicard();
|
||||
this.currentBoard.setAllowsBadgeAttachmentOnMinicard(newValue);
|
||||
$(`.js-field-has-badge-attachment-on-minicard ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsBadgeAttachmentOnMinicard,
|
||||
Utils.allowsBadgeAttachmentOnMinicard(),
|
||||
);
|
||||
$('.js-field-has-badge-attachment-on-minicard').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsBadgeAttachmentOnMinicard,
|
||||
Utils.allowsBadgeAttachmentOnMinicard(),
|
||||
);
|
||||
},
|
||||
'click .js-field-has-card-sorting-by-number-on-minicard'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsCardSortingByNumberOnMinicard = !this.currentBoard
|
||||
.allowsCardSortingByNumberOnMinicard;
|
||||
this.currentBoard.setallowsCardSortingByNumberOnMinicard(
|
||||
this.currentBoard.allowsCardSortingByNumberOnMinicard,
|
||||
);
|
||||
const newValue = !Utils.allowsCardSortingByNumberOnMinicard();
|
||||
this.currentBoard.setAllowsCardSortingByNumberOnMinicard(newValue);
|
||||
$(`.js-field-has-card-sorting-by-number-on-minicard ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCardSortingByNumberOnMinicard,
|
||||
Utils.allowsCardSortingByNumberOnMinicard(),
|
||||
);
|
||||
$('.js-field-has-card-sorting-by-number-on-minicard').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCardSortingByNumberOnMinicard,
|
||||
Utils.allowsCardSortingByNumberOnMinicard(),
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
@ -1541,12 +1342,13 @@ BlazeComponent.extendComponent({
|
|||
'keyup .js-search-member-input'(event) {
|
||||
Session.set('addMemberPopup.error', '');
|
||||
const query = event.target.value.trim();
|
||||
|
||||
this.searchQuery.set(query);
|
||||
|
||||
// Clear previous timeout
|
||||
if (this.searchTimeout) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
}
|
||||
|
||||
|
||||
// Debounce search
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.performSearch(query);
|
||||
|
|
@ -2061,4 +1863,3 @@ Template.changePermissionsPopup.helpers({
|
|||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
input {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
@ -14,11 +14,8 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
clickOnMiniCard(evt) {
|
||||
if (Utils.isMiniScreen()) {
|
||||
evt.preventDefault();
|
||||
Session.set('popupCardId', this.currentData()._id);
|
||||
this.cardDetailsPopup(evt);
|
||||
}
|
||||
evt.preventDefault();
|
||||
Session.set('popupCardId', this.currentData()._id);
|
||||
},
|
||||
|
||||
cardDetailsPopup(event) {
|
||||
|
|
|
|||
|
|
@ -9,41 +9,37 @@ template(name="swimlaneHeader")
|
|||
+swimlaneFixedHeader(this)
|
||||
|
||||
template(name="swimlaneFixedHeader")
|
||||
.swimlane-header(
|
||||
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
|
||||
if $eq title 'Card Templates'
|
||||
| {{_ 'card-templates-swimlane'}}
|
||||
else if $eq title 'List Templates'
|
||||
| {{_ 'list-templates-swimlane'}}
|
||||
else if $eq title 'Board Templates'
|
||||
| {{_ 'board-templates-swimlane'}}
|
||||
else if $eq title 'Default'
|
||||
| {{_ 'defaultdefault'}}
|
||||
else
|
||||
+viewer
|
||||
| {{isTitleDefault title}}
|
||||
.swimlane-header-menu
|
||||
.swimlane-header-menu-left
|
||||
if currentUser
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
unless currentUser.isWorker
|
||||
a.swimlane-collapse-indicator.js-collapse-swimlane.swimlane-header-collapse(title="{{_ 'collapse'}}")
|
||||
if collapseSwimlane
|
||||
i.fa.fa-caret-right
|
||||
else
|
||||
i.fa.fa-caret-down
|
||||
a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
|
||||
i.fa.fa-plus
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
unless isTouchScreen
|
||||
a.swimlane-header-handle.handle.js-swimlane-header-handle
|
||||
i.fa.fa-arrows
|
||||
if isTouchScreen
|
||||
a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
|
||||
i.fa.fa-arrows
|
||||
unless currentUser.isWorker
|
||||
a.swimlane-collapse-indicator.js-collapse-swimlane.swimlane-header-collapse(title="{{_ 'collapse'}}")
|
||||
if collapseSwimlane
|
||||
i.fa.fa-caret-right
|
||||
else
|
||||
i.fa.fa-caret-down
|
||||
.swimlane-header(
|
||||
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
|
||||
if $eq title 'Card Templates'
|
||||
| {{_ 'card-templates-swimlane'}}
|
||||
else if $eq title 'List Templates'
|
||||
| {{_ 'list-templates-swimlane'}}
|
||||
else if $eq title 'Board Templates'
|
||||
| {{_ 'board-templates-swimlane'}}
|
||||
else if $eq title 'Default'
|
||||
| {{_ 'defaultdefault'}}
|
||||
else
|
||||
+viewer
|
||||
| {{isTitleDefault title}}
|
||||
.swimlane-header-menu-right
|
||||
if currentUser
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isWorker
|
||||
a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
|
||||
i.fa.fa-bars
|
||||
if isMiniScreen
|
||||
a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
|
||||
i.fa.fa-arrows
|
||||
|
||||
template(name="editSwimlaneTitleForm")
|
||||
.list-composer
|
||||
|
|
@ -59,23 +55,23 @@ template(name="swimlaneActionPopup")
|
|||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
ul.pop-over-list
|
||||
li: a.js-add-swimlane
|
||||
i.fa.fa-plus
|
||||
span {{_ 'add-swimlane'}}
|
||||
li: a.js-add-swimlane
|
||||
i.fa.fa-plus
|
||||
span {{_ 'add-swimlane'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-add-list-from-swimlane
|
||||
i.fa.fa-plus
|
||||
span {{_ 'add-list'}}
|
||||
li: a.js-add-list-from-swimlane
|
||||
i.fa.fa-plus
|
||||
span {{_ 'add-list'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
if currentUser.isBoardAdmin
|
||||
li: a.js-set-swimlane-color
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ 'select-color'}}
|
||||
li: a.js-set-swimlane-height
|
||||
i.fa.fa-arrows
|
||||
| {{_ 'set-swimlane-height'}}
|
||||
if currentUser.isBoardAdmin
|
||||
li: a.js-set-swimlane-color
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ 'select-color'}}
|
||||
li: a.js-set-swimlane-height
|
||||
i.fa.fa-arrows
|
||||
| {{_ 'set-swimlane-height'}}
|
||||
if currentUser.isBoardAdmin
|
||||
unless this.isTemplateContainer
|
||||
hr
|
||||
|
|
@ -117,8 +113,7 @@ template(name="setSwimlaneColorPopup")
|
|||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
// Buttons aligned left too
|
||||
.flush-left
|
||||
.form-buttons
|
||||
button.primary.confirm.js-submit(style="margin-left:0") {{_ 'save'}}
|
||||
button.js-remove-color.negate.wide.right(style="margin-left:8px") {{_ 'unset-color'}}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ Template.editSwimlaneTitleForm.helpers({
|
|||
// When that happens, try use translation "defaultdefault" that has same content of default, or return text "Default".
|
||||
// This can happen, if swimlane does not have name.
|
||||
// Yes, this is fixing the symptom (Swimlane title does not have title)
|
||||
// instead of fixing the problem (Add Swimlane title when creating swimlane)
|
||||
// instead of fixing the problem (Add Swimlane title when creating swimlane)
|
||||
// because there could be thousands of swimlanes, adding name Default to all of them
|
||||
// would be very slow.
|
||||
if (title.startsWith("key 'default") && title.endsWith('returned an object instead of string.')) {
|
||||
|
|
|
|||
|
|
@ -1,39 +1,29 @@
|
|||
[class=swimlane] {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
}
|
||||
.swimlane {
|
||||
.swimlane.js-lists{
|
||||
background: #dedede;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
height: var(--swimlane-height, auto);
|
||||
min-height: var(--swimlane-min-height, 200px);
|
||||
}
|
||||
.swimlane.js-lists.js-swimlane {
|
||||
min-height: 150px;
|
||||
|
||||
body.mobile-mode .swimlane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
.swimlane-header {
|
||||
font-size: var(--header-scale);
|
||||
}
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-collapse-down {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
position: absolute;
|
||||
top: 0.7vh;
|
||||
left: 13vw;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-collapse-up {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
position: absolute;
|
||||
bottom: 0.7vh;
|
||||
left: 13vw;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-uncollapse-up {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-uncollapse-down {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
|
||||
.swimlane-container {
|
||||
background-color: #ccc;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
/* default to the same as lists to avoid contrast with the handle */
|
||||
background: #dedede;
|
||||
}
|
||||
.swimlane.placeholder {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
|
|
@ -50,30 +40,28 @@
|
|||
cursor: grabbing;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1 0 100%;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: max-content;
|
||||
padding: 0.5lh 1ch;
|
||||
background-color: #ccc;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
min-height: 33px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
position: sticky;
|
||||
left: 0;
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.swimlane .swimlane-header-wrap .swimlane-header {
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
min-height: 33px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
z-index: 10;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
|
|
@ -81,87 +69,30 @@
|
|||
justify-content: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu {
|
||||
position: absolute;
|
||||
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: 30px;
|
||||
color: #a6a6a6;
|
||||
vertical-align: middle;
|
||||
line-height: 1.2;
|
||||
|
||||
.swimlane {
|
||||
.swimlane-header-menu-right, .swimlane-header-menu-left {
|
||||
display: inline-flex;
|
||||
align-content: center;
|
||||
gap: 2ch;
|
||||
}
|
||||
/* can't resize beyond that point, but resizing screen causes
|
||||
overflow, which is great because lists would shrink too much otherwise */
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu {
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-plus-icon {
|
||||
top: calc(50% + 6px);
|
||||
padding: 5px;
|
||||
margin-left: 20px;
|
||||
font-size: 22px;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu-icon {
|
||||
top: calc(50% + 6px);
|
||||
padding-left: 5px;
|
||||
font-size: 22px;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-handle {
|
||||
position: relative;
|
||||
top: calc(50% + 2px);
|
||||
padding: 2px 5px;
|
||||
font-size: clamp(16px, 3vw, 20px);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: 30px;
|
||||
cursor: move;
|
||||
pointer-events: auto;
|
||||
color: #a6a6a6;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-miniscreen-handle {
|
||||
position: relative;
|
||||
padding: 2px 5px;
|
||||
top: calc(50% + 2px);
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: 30px;
|
||||
cursor: move;
|
||||
pointer-events: auto;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
/* Swimlane collapse button styling - matches list collapse button */
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu .swimlane-collapse-indicator {
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-handle {
|
||||
cursor: move;
|
||||
pointer-events: auto;
|
||||
color: #a6a6a6;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 5px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu .swimlane-collapse-indicator:hover {
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu-right .swimlane-collapse-indicator:hover {
|
||||
background-color: transparent;
|
||||
color: #333;
|
||||
}
|
||||
|
|
@ -290,105 +221,75 @@
|
|||
color: #fff !important;
|
||||
}
|
||||
|
||||
body.mobile-mode {
|
||||
.swimlane-resize-handle {
|
||||
height: 2ch;
|
||||
:active {
|
||||
background: rgba(0, 123, 255, 0.4) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
body.mobile-mode {
|
||||
.swimlane-resize-handle {
|
||||
height: 1lh;
|
||||
}
|
||||
}
|
||||
/* Swimlane resize handle */
|
||||
.swimlane-resize-handle {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 8px;
|
||||
background: transparent;
|
||||
height: max(0.7ch, 0.3lh);
|
||||
cursor: row-resize;
|
||||
z-index: 20;
|
||||
border-top: 2px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 2px;
|
||||
/* Ensure the handle is clickable */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Show resize handle only on hover */
|
||||
.swimlane:hover .swimlane-resize-handle {
|
||||
/* Prevent scrolling behaviour on click */
|
||||
touch-action: none;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Add a subtle resize indicator line at the bottom of swimlane on hover */
|
||||
.swimlane:hover .swimlane-resize-handle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: rgba(0, 123, 255, 0.3);
|
||||
z-index: 21;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* Make the indicator line more prominent when hovering over the resize handle */
|
||||
.swimlane-resize-handle:hover::after {
|
||||
background: rgba(0, 123, 255, 0.6) !important;
|
||||
height: 3px !important;
|
||||
box-shadow: 0 0 4px rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.swimlane-resize-handle:hover {
|
||||
background: rgba(0, 123, 255, 0.4) !important;
|
||||
border-top-color: #0079bf !important;
|
||||
box-shadow: 0 0 4px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.swimlane-resize-handle:active {
|
||||
background: rgba(0, 123, 255, 0.6) !important;
|
||||
border-top-color: #0079bf !important;
|
||||
box-shadow: 0 0 6px rgba(0, 123, 255, 0.4);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Add a subtle indicator line */
|
||||
.swimlane-resize-handle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
left: 50vw;
|
||||
width: 20px;
|
||||
height: 2px;
|
||||
background: rgba(0, 123, 255, 0.6);
|
||||
border-radius: 1px;
|
||||
height: 1px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.swimlane-resize-handle:hover::before {
|
||||
opacity: 1;
|
||||
.swimlane.swimlane-resizing + .swimlane-resize-handle:hover::before, .swimlane-resize-handle:hover::before {
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
/* Visual feedback during resize */
|
||||
.swimlane.swimlane-resizing {
|
||||
transition: none !important;
|
||||
box-shadow: 0 0 10px rgba(0, 123, 255, 0.3);
|
||||
/* Ensure the swimlane maintains its new height during resize */
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
/* Override any conflicting layout properties */
|
||||
display: flex !important;
|
||||
position: relative !important;
|
||||
/* Force height to be respected */
|
||||
height: var(--swimlane-height, auto) !important;
|
||||
min-height: var(--swimlane-height, auto) !important;
|
||||
max-height: var(--swimlane-height, auto) !important;
|
||||
/* Ensure the height is applied immediately */
|
||||
overflow: visible !important;
|
||||
.swimlane:not(.cannot-resize) {
|
||||
/* Add a subtle resize indicator line at the bottom of swimlane on hover */
|
||||
&:hover + .swimlane-resize-handle, + .swimlane-resize-handle:hover {
|
||||
border-top: 1px solid rgba(0, 123, 255, 0.5);
|
||||
background: rgba(0, 123, 255, 0.2);
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.swimlane.swimlane-resizing + .swimlane-resize-handle {
|
||||
background: rgba(0, 123, 255, 0.4) !important;
|
||||
}
|
||||
|
||||
.swimlane.cannot-resize + .swimlane-resize-handle {
|
||||
background: rgba(227, 64, 83, 0.5) !important;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
body.swimlane-resizing-active {
|
||||
cursor: row-resize !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
body.swimlane-resizing-active * {
|
||||
cursor: row-resize !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,38 @@
|
|||
template(name="swimlane")
|
||||
.swimlane.nodragscroll
|
||||
+swimlaneHeader
|
||||
unless collapseSwimlane
|
||||
.swimlane.js-lists.js-swimlane.dragscroll(id="swimlane-{{_id}}"
|
||||
style="height:{{swimlaneHeight}};")
|
||||
.swimlane-resize-handle.js-swimlane-resize-handle.nodragscroll
|
||||
if isMiniScreen
|
||||
if currentListIsInThisSwimlane _id
|
||||
+list(currentList)
|
||||
unless currentList
|
||||
.swimlane-container
|
||||
.swimlane.nodragscroll
|
||||
+swimlaneHeader
|
||||
unless collapseSwimlane
|
||||
.swimlane.js-lists.js-swimlane.dragscroll(id="swimlane-{{_id}}")
|
||||
if isMiniScreen
|
||||
each lists
|
||||
+miniList(this)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
else
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
each lists
|
||||
+miniList(this)
|
||||
else
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
each lists
|
||||
if visible this
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id ../_id
|
||||
+cardDetails(currentCard)
|
||||
if visible this
|
||||
+list(this)
|
||||
//- allow resizing in mobile mode
|
||||
.swimlane-resize-handle.js-swimlane-resize-handle.nodragscroll
|
||||
|
||||
template(name="listsGroup")
|
||||
.swimlane.list-group.js-lists.dragscroll
|
||||
if isMiniScreen
|
||||
if currentList
|
||||
+list(currentList)
|
||||
else
|
||||
each lists
|
||||
+miniList(this)
|
||||
each lists
|
||||
+miniList(this)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
else
|
||||
each lists
|
||||
if visible this
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id null
|
||||
+cardDetails(currentCard)
|
||||
.swimlane-resize-handle.js-swimlane-resize-handle.nodragscroll
|
||||
|
||||
template(name="addListForm")
|
||||
unless currentUser.isWorker
|
||||
|
|
@ -45,27 +40,27 @@ template(name="addListForm")
|
|||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
|
||||
.list-header-add
|
||||
+inlinedForm(autoclose=false)
|
||||
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
||||
autocomplete="off" autofocus)
|
||||
if currentBoard.getLastList
|
||||
| {{_ 'add-after-list'}}
|
||||
select.list-position-input.full-line
|
||||
each currentBoard.lists
|
||||
option(value="{{_id}}" selected=currentBoard.getLastList.title) {{title}}
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'save'}}
|
||||
.js-close-inlined-form
|
||||
i.fa.fa-times-thin
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-list-template {{_ 'template'}}
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form(title="{{_ 'add-list'}}")
|
||||
i.fa.fa-plus
|
||||
.list-header-add
|
||||
+inlinedForm(autoclose=false)
|
||||
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
||||
autocomplete="off" autofocus)
|
||||
if lists
|
||||
| {{_ 'add-after-list'}}
|
||||
select.list-position-input.full-line
|
||||
each lists
|
||||
option(value="{{_id}}" selected=currentBoard.getLastList.title) {{title}}
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'save'}}
|
||||
a.js-close-inlined-form
|
||||
i.fa.fa-times-thin
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-list-template {{_ 'template'}}
|
||||
else
|
||||
a.open-list-composer.list-header.js-open-inlined-form(title="{{_ 'add-list'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="moveSwimlanePopup")
|
||||
if currentUser
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
|||
import dragscroll from '@wekanteam/dragscroll';
|
||||
const { calculateIndex } = Utils;
|
||||
|
||||
function getBoardComponent() {
|
||||
// as list can be rendered from multiple inner elements, feels like a reliable
|
||||
// way to get the components having rendered the board
|
||||
return BlazeComponent.getComponentForElement(document.getElementsByClassName('board-canvas')[0]);
|
||||
}
|
||||
|
||||
function saveSorting(ui) {
|
||||
// To attribute the new index number, we need to get the DOM element
|
||||
// of the previous and the following list -- if any.
|
||||
|
|
@ -152,6 +158,12 @@ function currentListIsInThisSwimlane(swimlaneId) {
|
|||
);
|
||||
}
|
||||
|
||||
function currentList(listId, swimlaneId) {
|
||||
const list = Utils.getCurrentList();
|
||||
return list && list._id == listId && (list.swimlaneId === swimlaneId || list.swimlaneId === '');
|
||||
}
|
||||
|
||||
|
||||
function currentCardIsInThisList(listId, swimlaneId) {
|
||||
const currentCard = Utils.getCurrentCard();
|
||||
//const currentUser = ReactiveCache.getCurrentUser();
|
||||
|
|
@ -227,122 +239,63 @@ function syncListOrderFromStorage(boardId) {
|
|||
}
|
||||
};
|
||||
|
||||
function initSortable(boardComponent, $listsDom) {
|
||||
// Safety check: ensure we have valid DOM elements
|
||||
if (!$listsDom || $listsDom.length === 0) {
|
||||
console.error('initSortable: No valid DOM elements provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if sortable is already initialized
|
||||
if ($listsDom.data('uiSortable') || $listsDom.data('sortable')) {
|
||||
$listsDom.sortable('destroy');
|
||||
}
|
||||
|
||||
// We want to animate the card details window closing. We rely on CSS
|
||||
// transition for the actual animation.
|
||||
$listsDom._uihooks = {
|
||||
removeElement(node) {
|
||||
const removeNode = _.once(() => {
|
||||
node.parentNode.removeChild(node);
|
||||
});
|
||||
if ($(node).hasClass('js-card-details')) {
|
||||
$(node).css({
|
||||
flexBasis: 0,
|
||||
padding: 0,
|
||||
});
|
||||
$listsDom.one(CSSEvents.transitionend, removeNode);
|
||||
} else {
|
||||
removeNode();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// Add click debugging for drag handles
|
||||
$listsDom.on('mousedown', '.js-list-handle', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$listsDom.on('mousedown', '.js-list-header', function(e) {
|
||||
});
|
||||
|
||||
// Add debugging for any mousedown on lists
|
||||
$listsDom.on('mousedown', '.js-list', function(e) {
|
||||
});
|
||||
|
||||
// Add debugging for sortable events
|
||||
$listsDom.on('sortstart', function(e, ui) {
|
||||
});
|
||||
|
||||
$listsDom.on('sortbeforestop', function(e, ui) {
|
||||
});
|
||||
|
||||
$listsDom.on('sortstop', function(e, ui) {
|
||||
});
|
||||
|
||||
try {
|
||||
$listsDom.sortable({
|
||||
connectWith: '.js-swimlane, .js-lists',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper(evt, item) {
|
||||
const helper = item.clone();
|
||||
helper.css('z-index', 1000);
|
||||
return helper;
|
||||
},
|
||||
items: '.js-list:not(.js-list-composer)',
|
||||
placeholder: 'list placeholder',
|
||||
distance: 3,
|
||||
forcePlaceholderSize: true,
|
||||
cursor: 'move',
|
||||
start(evt, ui) {
|
||||
ui.helper.css('z-index', 1000);
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
ui.placeholder.width(ui.helper.width());
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
boardComponent.setIsDragging(true);
|
||||
|
||||
// Add visual feedback for list being dragged
|
||||
ui.item.addClass('ui-sortable-helper');
|
||||
|
||||
// Disable dragscroll during list dragging to prevent interference
|
||||
try {
|
||||
dragscroll.reset();
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
// Also disable dragscroll on all swimlanes during list dragging
|
||||
$('.js-swimlane').each(function() {
|
||||
$(this).removeClass('dragscroll');
|
||||
});
|
||||
},
|
||||
beforeStop(evt, ui) {
|
||||
// Clean up visual feedback
|
||||
ui.item.removeClass('ui-sortable-helper');
|
||||
},
|
||||
stop(evt, ui) {
|
||||
saveSorting(ui);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing list sortable:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if drag handles exist
|
||||
const dragHandles = $listsDom.find('.js-list-handle');
|
||||
|
||||
// Check if lists exist
|
||||
const lists = $listsDom.find('.js-list');
|
||||
|
||||
// Skip the complex autorun and options for now
|
||||
}
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
||||
initializeSortableLists() {
|
||||
let boardComponent = getBoardComponent();
|
||||
|
||||
// needs to be run again on uncollapsed
|
||||
const handleSelector = Utils.isMiniScreen()
|
||||
? '.js-list-handle'
|
||||
: '.list-header-name-container';
|
||||
const $lists = this.$('.js-list');
|
||||
const $parent = $lists.parent();
|
||||
|
||||
if ($lists.length > 0) {
|
||||
|
||||
// Check for drag handles
|
||||
const $handles = $parent.find(handleSelector);
|
||||
|
||||
// Test if drag handles are clickable
|
||||
$handles.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$parent.sortable({
|
||||
connectWith: '.js-swimlane, .js-lists',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper: 'clone',
|
||||
items: '.js-list',
|
||||
placeholder: 'list placeholder',
|
||||
distance: 7,
|
||||
handle: handleSelector,
|
||||
disabled: !Utils.canModifyBoard(),
|
||||
start(evt, ui) {
|
||||
ui.helper.css('z-index', 1000);
|
||||
width = ui.helper.width();
|
||||
height = ui.helper.height();
|
||||
ui.placeholder.height(height);
|
||||
ui.placeholder.width(width);
|
||||
ui.placeholder[0].setAttribute('style', `width: ${width}px !important; height: ${height}px !important;`);
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
boardComponent.setIsDragging(true);
|
||||
},
|
||||
stop(evt, ui) {
|
||||
boardComponent.setIsDragging(false);
|
||||
saveSorting(ui);
|
||||
},
|
||||
sort(event, ui) {
|
||||
Utils.scrollIfNeeded(event);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
const boardComponent = this.parentComponent();
|
||||
// can be rendered from either swimlane or board; check with DOM class heuristic,
|
||||
const $listsDom = this.$('.js-lists');
|
||||
// Sync list order from localStorage on board load
|
||||
const boardId = Session.get('currentBoard');
|
||||
|
|
@ -353,68 +306,18 @@ BlazeComponent.extendComponent({
|
|||
}, 500);
|
||||
}
|
||||
|
||||
|
||||
if (!Utils.getCurrentCardId()) {
|
||||
boardComponent.scrollLeft();
|
||||
}
|
||||
|
||||
// Try a simpler approach - initialize sortable directly like cards do
|
||||
this.initializeSwimlaneResize();
|
||||
|
||||
// Wait for DOM to be ready
|
||||
setTimeout(() => {
|
||||
const handleSelector = Utils.isTouchScreenOrShowDesktopDragHandles()
|
||||
? '.js-list-handle'
|
||||
: '.js-list-header';
|
||||
const $lists = this.$('.js-list');
|
||||
setTimeout(this.initializeSortableLists, 100);
|
||||
|
||||
const $parent = $lists.parent();
|
||||
|
||||
if ($lists.length > 0) {
|
||||
|
||||
// Check for drag handles
|
||||
const $handles = $parent.find('.js-list-handle');
|
||||
|
||||
// Test if drag handles are clickable
|
||||
$handles.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$parent.sortable({
|
||||
connectWith: '.js-swimlane, .js-lists',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper: 'clone',
|
||||
items: '.js-list:not(.js-list-composer)',
|
||||
placeholder: 'list placeholder',
|
||||
distance: 7,
|
||||
handle: handleSelector,
|
||||
disabled: !Utils.canModifyBoard(),
|
||||
start(evt, ui) {
|
||||
ui.helper.css('z-index', 1000);
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
ui.placeholder.width(ui.helper.width());
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
boardComponent.setIsDragging(true);
|
||||
},
|
||||
stop(evt, ui) {
|
||||
boardComponent.setIsDragging(false);
|
||||
saveSorting(ui);
|
||||
}
|
||||
});
|
||||
// Reactively update handle when user toggles desktop drag handles
|
||||
this.autorun(() => {
|
||||
const newHandle = Utils.isTouchScreenOrShowDesktopDragHandles()
|
||||
? '.js-list-handle'
|
||||
: '.js-list-header';
|
||||
if ($parent.data('uiSortable') || $parent.data('sortable')) {
|
||||
try { $parent.sortable('option', 'handle', newHandle); } catch (e) {}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// React to uncollapse (data is always reactive)
|
||||
this.autorun(() => {
|
||||
if (!this.currentData().isCollapsed()) {
|
||||
this.initializeSortableLists();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
onCreated() {
|
||||
this.draggingActive = new ReactiveVar(false);
|
||||
|
|
@ -465,7 +368,7 @@ BlazeComponent.extendComponent({
|
|||
// his mouse.
|
||||
|
||||
const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
|
||||
Utils.isTouchScreenOrShowDesktopDragHandles()
|
||||
Utils.isMiniScreen()
|
||||
? ['.js-list-handle', '.js-swimlane-header-handle']
|
||||
: ['.js-list-header'],
|
||||
).concat([
|
||||
|
|
@ -477,7 +380,7 @@ BlazeComponent.extendComponent({
|
|||
const isInNoDragArea = $(evt.target).closest(noDragInside.join(',')).length > 0;
|
||||
|
||||
if (isResizeHandle) {
|
||||
return;
|
||||
//return;
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -512,6 +415,11 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
swimlaneHeight() {
|
||||
// Using previous size with so much collasped/vertical logic will probably
|
||||
// be worst that letting layout takes needed space given the opened list each time
|
||||
if (Utils.isMiniScreen()) {
|
||||
return;
|
||||
}
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const swimlane = Template.currentData();
|
||||
|
||||
|
|
@ -552,7 +460,7 @@ BlazeComponent.extendComponent({
|
|||
|
||||
const swimlane = Template.currentData();
|
||||
const $swimlane = $(`#swimlane-${swimlane._id}`);
|
||||
const $resizeHandle = $swimlane.find('.js-swimlane-resize-handle');
|
||||
const $resizeHandle = $swimlane.siblings('.js-swimlane-resize-handle');
|
||||
|
||||
// Check if elements exist
|
||||
if (!$swimlane.length || !$resizeHandle.length) {
|
||||
|
|
@ -570,76 +478,190 @@ BlazeComponent.extendComponent({
|
|||
return;
|
||||
}
|
||||
|
||||
const isTouchScreen = Utils.isTouchScreen();
|
||||
let isResizing = false;
|
||||
let startY = 0;
|
||||
let startHeight = 0;
|
||||
const minHeight = 100;
|
||||
const maxHeight = 2000;
|
||||
const minHeight = Utils.isMiniScreen() ? 200 : 50;
|
||||
const absoluteMaxHeight = 2000;
|
||||
let computingHeight;
|
||||
let frame;
|
||||
|
||||
let fullHeight, maxHeight;
|
||||
let pageY, screenY, deltaY;
|
||||
|
||||
// how to do cleaner?
|
||||
const flexContainer = document.getElementsByClassName('swim-flex')[0];
|
||||
// only for cosmetic
|
||||
let maxHeightWithTolerance;
|
||||
const tolerance = 30;
|
||||
let previousLimit = false;
|
||||
|
||||
$swimlane[0].style.setProperty('--swimlane-min-height', `${minHeight}px`);
|
||||
// avoid jump effect and ensure height stays consistent
|
||||
// ⚠️ here, I propose to ignore saved height if it is not filled by content.
|
||||
// having large portions of blank lists makes the layout strange and hard to
|
||||
// navigate; also, the height changes a lot between different views, so it
|
||||
// feels ok to use the size as a hint, not as an absolute (as a user also)
|
||||
const unconstraignedHeight = $swimlane[0].getBoundingClientRect().height;
|
||||
const userHeight = parseFloat(this.swimlaneHeight(), 10);
|
||||
const preferredHeight = Math.min(userHeight, absoluteMaxHeight, unconstraignedHeight);
|
||||
$swimlane[0].style.setProperty('--swimlane-height', `${preferredHeight}px`);
|
||||
|
||||
const startResize = (e) => {
|
||||
isResizing = true;
|
||||
startY = e.pageY || e.originalEvent.touches[0].pageY;
|
||||
startHeight = parseInt($swimlane.css('height')) || 300;
|
||||
// gain access to modern attributes e.g. isPrimary
|
||||
e = e.originalEvent;
|
||||
|
||||
|
||||
$swimlane.addClass('swimlane-resizing');
|
||||
$('body').addClass('swimlane-resizing-active');
|
||||
$('body').css('user-select', 'none');
|
||||
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const doResize = (e) => {
|
||||
if (!isResizing) {
|
||||
if (isResizing || !(e.isPrimary && (e.pointerType !== 'mouse' || e.button === 0))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = e.pageY || e.originalEvent.touches[0].pageY;
|
||||
const deltaY = currentY - startY;
|
||||
const newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
|
||||
waitHeight(e, startResizeKnowingHeight);
|
||||
};
|
||||
|
||||
// unsure about this one; this is a way to compute what would be a "fit-content" height,
|
||||
// so that user cannot drag the swimlane too far. to do so, we clone the swimlane add
|
||||
// add it to the body, taking care of catching the frame just before it would be rendered.
|
||||
// it is well supported by browsers and adds extra-computation only once, when start dragging,
|
||||
// but still it feels odd.
|
||||
// the reason we cannot use initial, computed height is because it could have changed because
|
||||
// on new cards, thus constraining dragging too much. it is simple for list, add "real" unconstrained
|
||||
// width do not update on adding cards.
|
||||
const waitHeight = (e, callback) => {
|
||||
const computeSwimlaneHeight = (_) => {
|
||||
if (!computingHeight) {
|
||||
computingHeight = $swimlane[0].cloneNode(true);
|
||||
computingHeight.id = "clonedSwimlane";
|
||||
$(computingHeight).attr('style', 'height: auto !important; position: absolute');
|
||||
frame = requestAnimationFrame(computeSwimlaneHeight);
|
||||
document.body.appendChild(computingHeight);
|
||||
return;
|
||||
}
|
||||
catchBeforeRender = document.getElementById('clonedSwimlane');
|
||||
if (catchBeforeRender) {
|
||||
fullHeight = catchBeforeRender.offsetHeight;
|
||||
if (fullHeight > 0) {
|
||||
cancelAnimationFrame(frame);
|
||||
document.body.removeChild(computingHeight);
|
||||
computingHeight = undefined;
|
||||
frame = undefined;
|
||||
callback(e, fullHeight);
|
||||
return;
|
||||
}
|
||||
}
|
||||
frame = requestAnimationFrame(computeSwimlaneHeight);
|
||||
}
|
||||
computeSwimlaneHeight();
|
||||
}
|
||||
|
||||
// Apply the new height immediately for real-time feedback
|
||||
$swimlane[0].style.setProperty('--swimlane-height', `${newHeight}px`);
|
||||
$swimlane[0].style.setProperty('height', `${newHeight}px`);
|
||||
$swimlane[0].style.setProperty('min-height', `${newHeight}px`);
|
||||
$swimlane[0].style.setProperty('max-height', `${newHeight}px`);
|
||||
$swimlane[0].style.setProperty('flex', 'none');
|
||||
$swimlane[0].style.setProperty('flex-basis', 'auto');
|
||||
$swimlane[0].style.setProperty('flex-grow', '0');
|
||||
$swimlane[0].style.setProperty('flex-shrink', '0');
|
||||
const startResizeKnowingHeight = (e, height) => {
|
||||
document.addEventListener('pointermove', doResize);
|
||||
// e.g. debugger can cancel event without pointerup being fired
|
||||
// document.addEventListener('pointercancel', stopResize);
|
||||
document.addEventListener('pointerup', stopResize);
|
||||
// unavailable on e.g. Safari but mostly for smoothness
|
||||
document.addEventListener('wheel', doResize);
|
||||
|
||||
// --swimlane-height can be either a stored size or "auto"; get actual computed size
|
||||
currentHeight = $swimlane[0].offsetHeight;
|
||||
$swimlane.addClass('swimlane-resizing');
|
||||
$('body').addClass('swimlane-resizing-active');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// not being able to resize can be frustrating, give a little more room
|
||||
maxHeight = Math.max(height, absoluteMaxHeight);
|
||||
maxHeightWithTolerance = maxHeight + tolerance;
|
||||
|
||||
$swimlane[0].style.setProperty('--swimlane-max-height', `${maxHeightWithTolerance}px`);
|
||||
|
||||
pageY = e.pageY;
|
||||
|
||||
isResizing = true;
|
||||
previousLimit = false;
|
||||
deltaY = null;
|
||||
}
|
||||
|
||||
const doResize = (e) => {
|
||||
if (!isResizing || !(e.isPrimary || e instanceof WheelEvent)) {
|
||||
return;
|
||||
}
|
||||
const { y: handleY, height: handleHeight } = $resizeHandle[0].getBoundingClientRect();
|
||||
const containerHeight = flexContainer.offsetHeight;
|
||||
const isBlocked = $swimlane[0].classList.contains('cannot-resize');
|
||||
|
||||
// deltaY of WheelEvent is unreliable, do with a simple actual delta with handle and pointer
|
||||
deltaY = e.clientY - handleY;
|
||||
|
||||
const candidateHeight = currentHeight + deltaY;
|
||||
const oldHeight = currentHeight;
|
||||
let stepHeight = Math.max(minHeight, Math.min(maxHeightWithTolerance, candidateHeight));
|
||||
|
||||
const reachingMax = (maxHeightWithTolerance - stepHeight - 20) <= 0;
|
||||
const reachingMin = (stepHeight - 20 - minHeight) <= 0;
|
||||
if (!previousLimit && (reachingMax && deltaY > 0 || reachingMin && deltaY < 0)) {
|
||||
$swimlane[0].classList.add('cannot-resize');
|
||||
previousLimit = true;
|
||||
if (reachingMax) {
|
||||
stepHeight = maxHeightWithTolerance;
|
||||
} else {
|
||||
stepHeight = minHeight;
|
||||
}
|
||||
} else if (previousLimit && !reachingMax && !reachingMin) {
|
||||
// we want to re-init only below handle if min-size, above if max-size,
|
||||
// so computed values are accurate
|
||||
if ((deltaY > 0 && pageY >= handleY + handleHeight)
|
||||
|| (deltaY < 0 && pageY <= handleY)) {
|
||||
$swimlane[0].classList.remove('cannot-resize');
|
||||
// considered as a new move, changing direction is certain
|
||||
previousLimit = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBlocked) {
|
||||
// Ensure container grows and shrinks with swimlanes, so you guess a sense of scrolling something
|
||||
if (e.pageY > (containerHeight - window.innerHeight)) {
|
||||
document.body.style.height = `${containerHeight + window.innerHeight / 4}px`;
|
||||
}
|
||||
// helps to scroll at the beginning/end of the page
|
||||
let gapToLeave = window.innerHeight / 10;
|
||||
const factor = isTouchScreen ? 6 : 7;
|
||||
if (e.clientY > factor * gapToLeave) {
|
||||
//correct but too laggy
|
||||
window.scrollBy({ top: gapToLeave, behavior: "smooth" });
|
||||
}
|
||||
// special case where scrolling down while
|
||||
// swimlane is stuck; feels weird
|
||||
else if (e.clientY < (10 - factor) * gapToLeave) {
|
||||
window.scrollBy({ top: -gapToLeave , behavior: "smooth"});
|
||||
}
|
||||
}
|
||||
|
||||
if (oldHeight !== stepHeight && !isBlocked) {
|
||||
// Apply the new height immediately for real-time feedback
|
||||
$swimlane[0].style.setProperty('--swimlane-height', `${stepHeight}px`);
|
||||
currentHeight = stepHeight;
|
||||
}
|
||||
};
|
||||
|
||||
const stopResize = (e) => {
|
||||
if (!isResizing) return;
|
||||
if(!isResizing) {
|
||||
return;
|
||||
}
|
||||
if (previousLimit) {
|
||||
$swimlane[0].classList.remove('cannot-resize');
|
||||
}
|
||||
|
||||
// hopefully be gentler on cpu
|
||||
document.removeEventListener('pointermove', doResize);
|
||||
document.removeEventListener('pointercancel', stopResize);
|
||||
document.removeEventListener('pointerup', stopResize);
|
||||
document.removeEventListener('wheel', doResize);
|
||||
|
||||
isResizing = false;
|
||||
|
||||
// Calculate final height
|
||||
const currentY = e.pageY || e.originalEvent.touches[0].pageY;
|
||||
const deltaY = currentY - startY;
|
||||
const finalHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
|
||||
|
||||
// Ensure the final height is applied
|
||||
let finalHeight = Math.min(parseInt($swimlane[0].style.getPropertyValue('--swimlane-height'), 10), maxHeight);
|
||||
$swimlane[0].style.setProperty('--swimlane-height', `${finalHeight}px`);
|
||||
$swimlane[0].style.setProperty('height', `${finalHeight}px`);
|
||||
$swimlane[0].style.setProperty('min-height', `${finalHeight}px`);
|
||||
$swimlane[0].style.setProperty('max-height', `${finalHeight}px`);
|
||||
$swimlane[0].style.setProperty('flex', 'none');
|
||||
$swimlane[0].style.setProperty('flex-basis', 'auto');
|
||||
$swimlane[0].style.setProperty('flex-grow', '0');
|
||||
$swimlane[0].style.setProperty('flex-shrink', '0');
|
||||
|
||||
// Remove visual feedback but keep the height
|
||||
$swimlane.removeClass('swimlane-resizing');
|
||||
$('body').removeClass('swimlane-resizing-active');
|
||||
$('body').css('user-select', '');
|
||||
|
||||
// Save the new height using the existing system
|
||||
const boardId = swimlane.boardId;
|
||||
|
|
@ -678,30 +700,15 @@ BlazeComponent.extendComponent({
|
|||
console.warn('Error saving swimlane height to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// Mouse events
|
||||
$resizeHandle.on('mousedown', startResize);
|
||||
$(document).on('mousemove', doResize);
|
||||
$(document).on('mouseup', stopResize);
|
||||
|
||||
// Touch events for mobile
|
||||
$resizeHandle.on('touchstart', startResize, { passive: false });
|
||||
$(document).on('touchmove', doResize, { passive: false });
|
||||
$(document).on('touchend', stopResize, { passive: false });
|
||||
|
||||
|
||||
// Prevent dragscroll interference
|
||||
$resizeHandle.on('mousedown', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// handle both pointer and touch
|
||||
$resizeHandle.on("pointerdown", startResize);
|
||||
},
|
||||
}).register('swimlane');
|
||||
|
||||
|
||||
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentBoard = Utils.getCurrentBoard();
|
||||
|
|
@ -709,6 +716,8 @@ BlazeComponent.extendComponent({
|
|||
this.currentBoard.isTemplatesBoard() &&
|
||||
this.currentData().isListTemplatesSwimlane();
|
||||
this.currentSwimlane = this.currentData();
|
||||
// so that lists can be filtered from Board methods
|
||||
this.currentBoard.swimlane = this.currentSwimlane;
|
||||
},
|
||||
|
||||
// Proxy
|
||||
|
|
@ -765,6 +774,13 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('addListForm');
|
||||
|
||||
|
||||
Template.addListForm.helpers({
|
||||
lists() {
|
||||
return this.myLists();
|
||||
}
|
||||
});
|
||||
|
||||
Template.swimlane.helpers({
|
||||
canSeeAddList() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
|
|
@ -777,16 +793,14 @@ Template.swimlane.helpers({
|
|||
|
||||
collapseSwimlane() {
|
||||
return Utils.getSwimlaneCollapseState(this);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// Initialize sortable on DOM elements
|
||||
setTimeout(() => {
|
||||
const $listsGroupElements = $('.list-group');
|
||||
const computeHandle = () => (
|
||||
Utils.isTouchScreenOrShowDesktopDragHandles() ? '.js-list-handle' : '.js-list-header'
|
||||
);
|
||||
const computeHandle = () => Utils.isMiniScreen() ? '.js-list-handle' : '.list-header-name-container';
|
||||
|
||||
// Initialize sortable on ALL listsGroup elements (even empty ones)
|
||||
$listsGroupElements.each(function(index) {
|
||||
|
|
@ -800,7 +814,7 @@ setTimeout(() => {
|
|||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper: 'clone',
|
||||
items: '.js-list:not(.js-list-composer)',
|
||||
items: '.js-list',
|
||||
placeholder: 'list placeholder',
|
||||
distance: 7,
|
||||
handle: computeHandle(),
|
||||
|
|
@ -820,29 +834,10 @@ setTimeout(() => {
|
|||
// Silent fail
|
||||
}
|
||||
},
|
||||
sort(event, ui) {
|
||||
Utils.scrollIfNeeded(event);
|
||||
},
|
||||
stop(evt, ui) {
|
||||
// To attribute the new index number, we need to get the DOM element
|
||||
// of the previous and the following list -- if any.
|
||||
const prevListDom = ui.item.prev('.js-list').get(0);
|
||||
const nextListDom = ui.item.next('.js-list').get(0);
|
||||
const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
|
||||
|
||||
const listDomElement = ui.item.get(0);
|
||||
if (!listDomElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
let list;
|
||||
try {
|
||||
list = Blaze.getData(listDomElement);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect if the list was dropped in a different swimlane
|
||||
const targetSwimlaneDom = ui.item.closest('.js-swimlane');
|
||||
let targetSwimlaneId = null;
|
||||
|
|
@ -949,18 +944,6 @@ setTimeout(() => {
|
|||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
|
||||
// Re-enable dragscroll after list dragging is complete
|
||||
try {
|
||||
dragscroll.reset();
|
||||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
|
||||
// Re-enable dragscroll on all swimlanes
|
||||
$('.js-swimlane').each(function() {
|
||||
$(this).addClass('dragscroll');
|
||||
});
|
||||
}
|
||||
});
|
||||
// Reactively adjust handle when setting changes
|
||||
|
|
@ -980,6 +963,7 @@ BlazeComponent.extendComponent({
|
|||
currentCardIsInThisList(listId, swimlaneId) {
|
||||
return currentCardIsInThisList(listId, swimlaneId);
|
||||
},
|
||||
|
||||
visible(list) {
|
||||
if (list.archived) {
|
||||
// Show archived list only when filter archive is on
|
||||
|
|
@ -1003,7 +987,7 @@ BlazeComponent.extendComponent({
|
|||
return true;
|
||||
},
|
||||
onRendered() {
|
||||
const boardComponent = this.parentComponent();
|
||||
let boardComponent = getBoardComponent();
|
||||
const $listsDom = this.$('.js-lists');
|
||||
|
||||
|
||||
|
|
@ -1015,25 +999,24 @@ BlazeComponent.extendComponent({
|
|||
|
||||
// Wait for DOM to be ready
|
||||
setTimeout(() => {
|
||||
const handleSelector = Utils.isTouchScreenOrShowDesktopDragHandles()
|
||||
const handleSelector = Utils.isMiniScreen()
|
||||
? '.js-list-handle'
|
||||
: '.js-list-header';
|
||||
: '.list-header-name-container';
|
||||
const $lists = this.$('.js-list');
|
||||
|
||||
const $parent = $lists.parent();
|
||||
const parent = $lists.parent();
|
||||
|
||||
if ($lists.length > 0) {
|
||||
|
||||
// Check for drag handles
|
||||
const $handles = $parent.find('.js-list-handle');
|
||||
const handles = $(parent).find(handleSelector);
|
||||
|
||||
// Test if drag handles are clickable
|
||||
$handles.on('click', function(e) {
|
||||
handles.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$parent.sortable({
|
||||
parent.sortable({
|
||||
connectWith: '.js-swimlane, .js-lists',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
|
|
@ -1045,18 +1028,25 @@ BlazeComponent.extendComponent({
|
|||
disabled: !Utils.canModifyBoard(),
|
||||
start(evt, ui) {
|
||||
ui.helper.css('z-index', 1000);
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
ui.placeholder.width(ui.helper.width());
|
||||
width = ui.helper.width();
|
||||
height = ui.helper.height();
|
||||
ui.placeholder.height(height);
|
||||
ui.placeholder.width(width);
|
||||
ui.placeholder[0].setAttribute('style', `width: ${width}px !important; height: ${height}px !important;`);
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
boardComponent.setIsDragging(true);
|
||||
},
|
||||
stop(evt, ui) {
|
||||
boardComponent.setIsDragging(false);
|
||||
}
|
||||
saveSorting(ui);
|
||||
},
|
||||
sort(event, ui) {
|
||||
Utils.scrollIfNeeded(event);
|
||||
},
|
||||
});
|
||||
// Reactively update handle when user toggles desktop drag handles
|
||||
this.autorun(() => {
|
||||
const newHandle = Utils.isTouchScreenOrShowDesktopDragHandles()
|
||||
const newHandle = Utils.isMiniScreen()
|
||||
? '.js-list-handle'
|
||||
: '.js-list-header';
|
||||
if ($parent.data('uiSortable') || $parent.data('sortable')) {
|
||||
|
|
|
|||
|
|
@ -1,47 +1,40 @@
|
|||
.member {
|
||||
border-radius: 3px;
|
||||
display: block;
|
||||
position: relative;
|
||||
float: left;
|
||||
height: clamp(24px, 3.5vw, 36px);
|
||||
width: clamp(24px, 3.5vw, 36px);
|
||||
margin: .3vh;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
text-decoration: none;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.member .avatar {
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.member .avatar.avatar-initials {
|
||||
height: 70%;
|
||||
width: 70%;
|
||||
padding: 15%;
|
||||
display: flex;
|
||||
background-color: #dbdbdb;
|
||||
color: #444;
|
||||
position: absolute;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 50%;
|
||||
padding: 0.2em;
|
||||
font-size: 0.9em;
|
||||
height: var(--label-height);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-self: flex-start;
|
||||
color: #111;
|
||||
margin: 0 0.2ch;
|
||||
}
|
||||
|
||||
.js-select-initials {
|
||||
justify-content: start;
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.member .avatar.avatar-image {
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.member .member-presence-status {
|
||||
background-color: #b3b3b3;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 50%;
|
||||
height: 7px;
|
||||
width: 7px;
|
||||
height: 1.2ch;
|
||||
width: 1.2ch;
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
transform: translate(1.6ch, 1.6ch);
|
||||
border: 1px solid #fff;
|
||||
z-index: 15;
|
||||
}
|
||||
|
|
@ -61,18 +54,6 @@
|
|||
background: #e44242;
|
||||
border-color: #f1dada;
|
||||
}
|
||||
.member .edit-avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
background: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
}
|
||||
.member .edit-avatar:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
|
@ -112,9 +93,4 @@
|
|||
}
|
||||
.mini-profile-info .info p {
|
||||
padding-top: 0;
|
||||
}
|
||||
.mini-profile-info .member {
|
||||
width: clamp(40px, 5vw, 60px);
|
||||
height: clamp(40px, 5vw, 60px);
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,8 +19,8 @@ template(name="userAvatar")
|
|||
i.fa.fa-pencil-square-o
|
||||
|
||||
template(name="userAvatarInitials")
|
||||
svg.avatar.avatar-initials(viewBox="0 0 {{viewPortWidth}} 15")
|
||||
text(x="50%" y="11" text-anchor="middle" dominant-baseline="middle" font-size="16")= initials
|
||||
.avatar-initials
|
||||
= initials
|
||||
|
||||
template(name="orgAvatar")
|
||||
a.member.orgOrTeamMember(class="js-member" title="{{orgData.orgDisplayName}}")
|
||||
|
|
|
|||
|
|
@ -1,109 +1,106 @@
|
|||
.auth-layout .at-form-landing-logo {
|
||||
width: min(249px, 32vw);
|
||||
margin: auto;
|
||||
margin-top: 6vh;
|
||||
margin-bottom: 2.5vh;
|
||||
.auth-container {
|
||||
display: grid;
|
||||
align-content: stretch;
|
||||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
justify-content: center;
|
||||
padding: 2lh 0;
|
||||
/* i.e. center horizontally */
|
||||
margin-inline: auto;;
|
||||
/* parent container has relative positionning */
|
||||
grid-template-columns: 100%;
|
||||
grid-template-rows: minmax(20vh, 300px) min-content 1fr;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.mobile-mode:has(.auth-container) {
|
||||
.auth-container {
|
||||
grid-template-columns: 90vw;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-logo {
|
||||
&, &>a:not(img), > img {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
flex: 1;
|
||||
max-width: max(30vw, 600px);
|
||||
gap: 1lh;
|
||||
margin-bottom: 1lh;
|
||||
max-height: 80vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.auth-layout .auth-dialog {
|
||||
width: min(275px, 36vw);
|
||||
padding: 3vh 3vw;
|
||||
margin: auto;
|
||||
margin-bottom: 2.5vh;
|
||||
background: #fff;
|
||||
font-size: 1.1em;
|
||||
border-radius: 0.4vw;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-bottom-color: #c2c2c2;
|
||||
box-shadow: 0 0.2vh 0.8vh rgba(0,0,0,0.3);
|
||||
padding: 0 2ch 0.5lh 2ch;
|
||||
white-space: wrap;
|
||||
/* try to override properties of non-flex forms
|
||||
without referring too much to classes and ids, as forms
|
||||
are dynamic */
|
||||
&, div:not(#legalNoticeDiv, .lds-roller, .password-input-container, :empty), form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1lh;
|
||||
>:not(.at-input) {
|
||||
gap: 0.4lh;
|
||||
}
|
||||
.at-input {
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
*:not(div) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-layout .auth-dialog .at-form .at-link {
|
||||
color: #17683a;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form label {
|
||||
margin-bottom: 0.4vh;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-input-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
align-self: stretch;
|
||||
grid-template-columns: 1fr 6ch;
|
||||
}
|
||||
.password-input-container input {
|
||||
flex: 1;
|
||||
padding-right: 55px; /* More room for the bigger button */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.password-toggle-btn {
|
||||
position: absolute;
|
||||
right: 5px; /* Adjusted for larger button */
|
||||
top: calc(50% - 26px); /* Moved up by 20px + 6px = 26px total */
|
||||
transform: translateY(-50%);
|
||||
background: #f8f8f8 !important;
|
||||
border: 1px solid #ddd !important;
|
||||
border-radius: 3px !important;
|
||||
color: #000 !important; /* Black color for the icon */
|
||||
cursor: pointer;
|
||||
padding: 8px 6px 8px 12px; /* 2x bigger padding, 6px less on right */
|
||||
font-size: 16px; /* 2x bigger font size */
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
line-height: 1;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
min-width: 40px; /* 2x bigger minimum width */
|
||||
min-height: 32px; /* 2x bigger minimum height */
|
||||
}
|
||||
/* Adjust position for login and register pages */
|
||||
.auth-layout .password-toggle-btn {
|
||||
top: calc(50% - 11px); /* Move 15px down for login/register */
|
||||
}
|
||||
.password-toggle-btn .eye-text {
|
||||
color: #000 !important;
|
||||
font-size: 16px !important;
|
||||
line-height: 1;
|
||||
filter: grayscale(100%);
|
||||
-webkit-filter: grayscale(100%);
|
||||
opacity: 0.8;
|
||||
}
|
||||
.eye-slash-line {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
pointer-events: none;
|
||||
stroke: #000;
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
}
|
||||
.password-toggle-btn:hover .eye-text {
|
||||
color: #000 !important;
|
||||
filter: grayscale(100%);
|
||||
-webkit-filter: grayscale(100%);
|
||||
opacity: 0.8;
|
||||
|
||||
body.mobile-mode {
|
||||
.auth-layout {
|
||||
max-height: unset;
|
||||
}
|
||||
.password-input-container {
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form button {
|
||||
width: 100%;
|
||||
background: #216694;
|
||||
color: #fff;
|
||||
min-height: 2lh;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form .at-title {
|
||||
background: #f7f7f7;
|
||||
margin: -3vh -3vw;
|
||||
padding: 2vh 3vw 0.7vh;
|
||||
margin-bottom: 2.5vh;
|
||||
border-bottom: 1px solid #dcdcdc;
|
||||
color: #4d4d4d;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form .at-signup-link,
|
||||
.auth-layout .auth-dialog .at-form .at-signin-link,
|
||||
.auth-layout .auth-dialog .at-form .at-forgotPwd {
|
||||
font-size: 0.9em;
|
||||
margin-top: 2vh;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form .at-signup-link .at-signUp,
|
||||
|
|
@ -113,43 +110,4 @@
|
|||
.auth-layout .auth-dialog .at-form .at-signin-link .at-signIn,
|
||||
.auth-layout .auth-dialog .at-form .at-forgotPwd .at-signIn {
|
||||
font-weight: bold;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form-lang {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form-lang .select-lang {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.auth-layout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.auth-layout .at-form-landing-logo {
|
||||
width: 125px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 20px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.auth-layout .at-form-landing-logo img {
|
||||
width: 125px;
|
||||
}
|
||||
.auth-layout .auth-dialog {
|
||||
width: calc(100% - 50px);
|
||||
height: calc(100% - 50px);
|
||||
padding: 25px;
|
||||
min-height: 380px;
|
||||
margin: 0px;
|
||||
margin-bottom: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form .at-title h3 {
|
||||
width: calc(100% - 125px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,106 +5,126 @@ template(name="headerUserBar")
|
|||
+userAvatar(userId=currentUser._id)
|
||||
unless isMiniScreen
|
||||
unless isSandstorm
|
||||
if currentUser.profile.fullname
|
||||
= currentUser.profile.fullname
|
||||
else
|
||||
= currentUser.username
|
||||
.avatar-user-fullname
|
||||
if currentUser.profile.fullname
|
||||
= currentUser.profile.fullname
|
||||
else
|
||||
= currentUser.username
|
||||
|
||||
template(name="memberMenuPopup")
|
||||
ul.pop-over-list
|
||||
with currentUser
|
||||
li
|
||||
a.js-toggle-grey-icons(href="#")
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ 'grey-icons'}}
|
||||
span
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ 'grey-icons'}}
|
||||
if currentUser.profile
|
||||
if currentUser.profile.GreyIcons
|
||||
i.fa.fa-check
|
||||
li
|
||||
a.js-my-cards(href="{{pathFor 'my-cards'}}")
|
||||
i.fa.fa-list
|
||||
| {{_ 'my-cards'}}
|
||||
span
|
||||
i.fa.fa-list
|
||||
| {{_ 'my-cards'}}
|
||||
li
|
||||
a.js-due-cards(href="{{pathFor 'due-cards'}}")
|
||||
i.fa.fa-calendar
|
||||
| {{_ 'dueCards-title'}}
|
||||
span
|
||||
i.fa.fa-calendar
|
||||
| {{_ 'dueCards-title'}}
|
||||
li
|
||||
a.js-global-search(href="{{pathFor 'global-search'}}")
|
||||
i.fa.fa-search
|
||||
| {{_ 'globalSearch-title'}}
|
||||
span
|
||||
i.fa.fa-search
|
||||
| {{_ 'globalSearch-title'}}
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
i.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
span
|
||||
i.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
li
|
||||
a(href="{{pathFor 'public'}}")
|
||||
i.fa.fa-globe
|
||||
| {{_ 'public'}}
|
||||
span
|
||||
i.fa.fa-globe
|
||||
| {{_ 'public'}}
|
||||
li
|
||||
a.board-header-btn.js-open-archived-board
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archives'}}
|
||||
a.js-open-archived-board
|
||||
span
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archives'}}
|
||||
li
|
||||
a.js-notifications-drawer-toggle
|
||||
i.fa.fa-bell
|
||||
| {{_ 'notifications'}}
|
||||
span
|
||||
i.fa.fa-bell
|
||||
| {{_ 'notifications'}}
|
||||
if currentSetting.customHelpLinkUrl
|
||||
li
|
||||
a(href="{{currentSetting.customHelpLinkUrl}}", title="{{_ 'help'}}", target="_blank", rel="noopener noreferrer")
|
||||
i.fa.fa-question-circle
|
||||
| {{_ 'help'}}
|
||||
span
|
||||
i.fa.fa-question-circle
|
||||
| {{_ 'help'}}
|
||||
unless currentUser.isWorker
|
||||
ul.pop-over-list
|
||||
li
|
||||
a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
|
||||
i.fa.fa-list
|
||||
| {{_ 'templates'}}
|
||||
span
|
||||
i.fa.fa-list
|
||||
| {{_ 'templates'}}
|
||||
if currentUser.isAdmin
|
||||
li
|
||||
a.js-go-setting(href="{{pathFor 'setting'}}")
|
||||
i.fa.fa-lock
|
||||
| {{_ 'admin-panel'}}
|
||||
span
|
||||
i.fa.fa-lock
|
||||
| {{_ 'admin-panel'}}
|
||||
hr
|
||||
if isSameDomainNameSettingValue
|
||||
li
|
||||
a.js-invite-people
|
||||
i.fa.fa-envelope
|
||||
| {{_ 'invite-people'}}
|
||||
span
|
||||
i.fa.fa-envelope
|
||||
| {{_ 'invite-people'}}
|
||||
if isNotOAuth2AuthenticationMethod
|
||||
li
|
||||
a.js-edit-profile
|
||||
i.fa.fa-user
|
||||
| {{_ 'edit-profile'}}
|
||||
span
|
||||
i.fa.fa-user
|
||||
| {{_ 'edit-profile'}}
|
||||
li
|
||||
a.js-change-settings
|
||||
i.fa.fa-cog
|
||||
| {{_ 'change-settings'}}
|
||||
span
|
||||
i.fa.fa-cog
|
||||
| {{_ 'change-settings'}}
|
||||
li
|
||||
a.js-change-avatar
|
||||
i.fa.fa-picture-o
|
||||
| {{_ 'edit-avatar'}}
|
||||
span
|
||||
i.fa.fa-picture-o
|
||||
| {{_ 'edit-avatar'}}
|
||||
unless isSandstorm
|
||||
if isNotOAuth2AuthenticationMethod
|
||||
li
|
||||
a.js-change-password
|
||||
i.fa.fa-key
|
||||
| {{_ 'changePasswordPopup-title'}}
|
||||
span
|
||||
i.fa.fa-key
|
||||
| {{_ 'changePasswordPopup-title'}}
|
||||
li
|
||||
a.js-change-language
|
||||
i.fa.fa-flag
|
||||
| {{_ 'changeLanguagePopup-title'}}
|
||||
span
|
||||
i.fa.fa-flag
|
||||
| {{_ 'changeLanguagePopup-title'}}
|
||||
if isSupportPageEnabled
|
||||
li
|
||||
a(href="{{pathFor 'support'}}")
|
||||
i.fa.fa-question-circle
|
||||
| {{_ 'support'}}
|
||||
span
|
||||
i.fa.fa-question-circle
|
||||
| {{_ 'support'}}
|
||||
unless isSandstorm
|
||||
hr
|
||||
ul.pop-over-list
|
||||
hr
|
||||
li
|
||||
a.js-logout
|
||||
i.fa.fa-sign-out
|
||||
| {{_ 'log-out'}}
|
||||
span
|
||||
i.fa.fa-sign-out
|
||||
| {{_ 'log-out'}}
|
||||
|
||||
template(name="invitePeoplePopup")
|
||||
ul#registration-setting.setting-detail
|
||||
|
|
@ -134,7 +154,7 @@ template(name="editProfilePopup")
|
|||
form
|
||||
label
|
||||
| {{_ 'fullname'}}
|
||||
input.js-profile-fullname(type="text" value=profile.fullname autofocus)
|
||||
input.js-profile-fullname(type="text" value=profile.fullname )
|
||||
label
|
||||
| {{_ 'username'}}
|
||||
span.error.hide.username-taken
|
||||
|
|
|
|||
|
|
@ -342,6 +342,7 @@ Template.changeLanguagePopup.events({
|
|||
},
|
||||
});
|
||||
TAPi18n.setLanguage(this.tag);
|
||||
Popup.close();
|
||||
event.preventDefault();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class BoardConverter {
|
|||
}
|
||||
|
||||
conversionStatus.set(`Converting ${listsToConvert.length} lists...`);
|
||||
|
||||
|
||||
const startTime = Date.now();
|
||||
const totalLists = listsToConvert.length;
|
||||
let convertedLists = 0;
|
||||
|
|
@ -122,20 +122,20 @@ class BoardConverter {
|
|||
const batchSize = 10;
|
||||
for (let i = 0; i < listsToConvert.length; i += batchSize) {
|
||||
const batch = listsToConvert.slice(i, i + batchSize);
|
||||
|
||||
|
||||
// Process batch
|
||||
await this.processBatch(batch, defaultSwimlane._id);
|
||||
|
||||
|
||||
convertedLists += batch.length;
|
||||
const progress = Math.round((convertedLists / totalLists) * 100);
|
||||
conversionProgress.set(progress);
|
||||
|
||||
|
||||
// Calculate estimated time remaining
|
||||
const elapsed = Date.now() - startTime;
|
||||
const rate = convertedLists / elapsed; // lists per millisecond
|
||||
const remaining = totalLists - convertedLists;
|
||||
const estimatedMs = remaining / rate;
|
||||
|
||||
|
||||
conversionStatus.set(`Converting list ${convertedLists} of ${totalLists}...`);
|
||||
conversionEstimatedTime.set(this.formatTime(estimatedMs));
|
||||
|
||||
|
|
@ -146,11 +146,11 @@ class BoardConverter {
|
|||
// Mark as converted
|
||||
this.conversionCache.set(boardId, true);
|
||||
globalConvertedBoards.add(boardId); // Mark board as converted
|
||||
|
||||
|
||||
conversionStatus.set('Board conversion completed!');
|
||||
conversionProgress.set(100);
|
||||
console.log(`Board ${boardId} conversion completed and marked as converted`);
|
||||
|
||||
|
||||
// Clear status after a delay
|
||||
setTimeout(() => {
|
||||
isConverting.set(false);
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ export class DialogWithBoardSwimlaneList extends BlazeComponent {
|
|||
*/
|
||||
getDefaultOption(boardId) {
|
||||
const ret = {
|
||||
'boardId' : "",
|
||||
'swimlaneId' : "",
|
||||
'listId' : "",
|
||||
'boardId' : this.data().boardId,
|
||||
'swimlaneId' : this.data().swimlaneId,
|
||||
'listId' : this.data().listId,
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -44,21 +44,20 @@ export class DialogWithBoardSwimlaneList extends BlazeComponent {
|
|||
let currentOptions = this.getDialogOptions();
|
||||
if (currentOptions && boardId && currentOptions[boardId]) {
|
||||
this.cardOption = currentOptions[boardId];
|
||||
if (this.cardOption.boardId &&
|
||||
this.cardOption.swimlaneId &&
|
||||
this.cardOption.listId
|
||||
)
|
||||
{
|
||||
this.selectedBoardId.set(this.cardOption.boardId)
|
||||
this.selectedSwimlaneId.set(this.cardOption.swimlaneId);
|
||||
this.selectedListId.set(this.cardOption.listId);
|
||||
}
|
||||
}
|
||||
if (this.cardOption.boardId &&
|
||||
this.cardOption.swimlaneId &&
|
||||
this.cardOption.listId
|
||||
) {
|
||||
this.selectedBoardId.set(this.cardOption.boardId)
|
||||
this.selectedSwimlaneId.set(this.cardOption.swimlaneId);
|
||||
this.selectedListId.set(this.cardOption.listId);
|
||||
}
|
||||
this.getBoardData(this.selectedBoardId.get());
|
||||
if (!this.selectedSwimlaneId.get() || !ReactiveCache.getSwimlane({_id: this.selectedSwimlaneId.get(), boardId: this.selectedBoardId.get()})) {
|
||||
if (this.selectedSwimlaneId.get() || ReactiveCache.getSwimlane({_id: this.selectedSwimlaneId.get(), boardId: this.selectedBoardId.get()})) {
|
||||
this.setFirstSwimlaneId();
|
||||
}
|
||||
if (!this.selectedListId.get() || !ReactiveCache.getList({_id: this.selectedListId.get(), boardId: this.selectedBoardId.get()})) {
|
||||
if (this.selectedListId.get() || ReactiveCache.getList({_id: this.selectedListId.get(), boardId: this.selectedBoardId.get()})) {
|
||||
this.setFirstListId();
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +73,7 @@ export class DialogWithBoardSwimlaneList extends BlazeComponent {
|
|||
setFirstListId() {
|
||||
try {
|
||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
||||
const listId = board.lists()[0]._id;
|
||||
const listId = board.listsInSwimlane(this.selectedSwimlaneId.get())[0]._id;
|
||||
this.selectedListId.set(listId);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
|
@ -131,7 +130,7 @@ export class DialogWithBoardSwimlaneList extends BlazeComponent {
|
|||
/** returns all available lists of the current board */
|
||||
lists() {
|
||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
||||
const ret = board.lists();
|
||||
const ret = board.listsInSwimlane(this.selectedSwimlaneId.get());
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -219,4 +218,3 @@ export class DialogWithBoardSwimlaneList extends BlazeComponent {
|
|||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export class DialogWithBoardSwimlaneListCard extends DialogWithBoardSwimlaneList
|
|||
*/
|
||||
setOption(boardId) {
|
||||
super.setOption(boardId);
|
||||
|
||||
|
||||
// Also set cardId if available
|
||||
if (this.cardOption && this.cardOption.cardId) {
|
||||
this.selectedCardId.set(this.cardOption.cardId);
|
||||
|
|
@ -69,7 +69,7 @@ export class DialogWithBoardSwimlaneListCard extends DialogWithBoardSwimlaneList
|
|||
|
||||
// reset list id
|
||||
self.setFirstListId();
|
||||
|
||||
|
||||
// reset card id
|
||||
self.selectedCardId.set('');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,10 +128,24 @@ hotkeys('escape', () => {
|
|||
Sidebar.hide();
|
||||
});
|
||||
|
||||
let currentMouseDown;
|
||||
|
||||
// Avoid the common issue of dragging an element a bit fast and releasing
|
||||
// out of the element; in that case e.g. popup closes, which is not pleasant.
|
||||
// Only execute actions if mousedown and mouseup are on the same element (the
|
||||
// initial issue is that a long drag is still a click event)
|
||||
$(document).on('pointerdown', evt => {
|
||||
currentMouseDown = evt.target;
|
||||
});
|
||||
// On a left click on the document, we try to exectute one escape action (eg,
|
||||
// close the popup). We don't execute any action if the user has clicked on a
|
||||
// link or a button.
|
||||
$(document).on('click', evt => {
|
||||
$(document).on('pointerup', evt => {
|
||||
const currentMouseUp = evt.target;
|
||||
if (currentMouseDown !== currentMouseUp) {
|
||||
// console.debug(`not executing escape actions on ${currentMouseUp} because click started on ${currentMouseDown}`);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
evt.button === 0 &&
|
||||
$(evt.target).closest('a,button,.is-editable').length === 0
|
||||
|
|
|
|||
|
|
@ -77,8 +77,28 @@ InlinedForm = BlazeComponent.extendComponent({
|
|||
return [
|
||||
{
|
||||
'click .js-close-inlined-form': this.close,
|
||||
'click .js-open-inlined-form': this.open,
|
||||
|
||||
'pointerdown .js-open-inlined-form'(e) {
|
||||
if (Utils.shouldIgnorePointer(e)) {
|
||||
return;
|
||||
}
|
||||
// to measure the click duration
|
||||
$(e.target).data("clickStart", new Date());
|
||||
},
|
||||
'pointerup .js-open-inlined-form'(e) {
|
||||
if(Utils.shouldIgnorePointer(e)) {
|
||||
return;
|
||||
}
|
||||
const start = $(e.target).data("clickStart",);
|
||||
if (!start) {
|
||||
return;
|
||||
}
|
||||
const end = new Date();
|
||||
// 500ms feels reasonable for a simple click
|
||||
if (end - start < 500) {
|
||||
this.open(e);
|
||||
}
|
||||
$(e.target).data("clickStart", null);
|
||||
},
|
||||
// Pressing Ctrl+Enter should submit the form
|
||||
'keydown form textarea'(evt) {
|
||||
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ hotkeys(nums, (event, handler) => {
|
|||
return;
|
||||
}
|
||||
const board = ReactiveCache.getBoard(currentBoardId);
|
||||
if (!board) {return}
|
||||
const labels = board.labels;
|
||||
if (MultiSelection.isActive() && ReactiveCache.getCurrentUser().isBoardMember()) {
|
||||
const cardIds = MultiSelection.getSelectedCardIds();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const closedValue = null;
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
|
||||
window.Modal = new (class {
|
||||
constructor() {
|
||||
this._currentModal = new ReactiveVar(closedValue);
|
||||
|
|
|
|||
|
|
@ -1,121 +1,25 @@
|
|||
import PopupComponent from '/client/components/main/popup';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
window.Popup = new (class {
|
||||
constructor() {
|
||||
// The template we use to render popups
|
||||
this.template = Template.popup;
|
||||
|
||||
// We only want to display one popup at a time and we keep the view object
|
||||
// in this `Popup.current` variable. If there is no popup currently opened
|
||||
// the value is `null`.
|
||||
this.current = null;
|
||||
|
||||
// It's possible to open a sub-popup B from a popup A. In that case we keep
|
||||
// the data of popup A so we can return back to it. Every time we open a new
|
||||
// popup the stack grows, every time we go back the stack decrease, and if
|
||||
// we close the popup the stack is reseted to the empty stack [].
|
||||
this._stack = [];
|
||||
|
||||
// We invalidate this internal dependency every time the top of the stack
|
||||
// has changed and we want to re-render a popup with the new top-stack data.
|
||||
this._dep = new Tracker.Dependency();
|
||||
}
|
||||
|
||||
/// This function returns a callback that can be used in an event map:
|
||||
/// Template.tplName.events({
|
||||
/// 'click .elementClass': Popup.open("popupName"),
|
||||
/// });
|
||||
/// The popup inherit the data context of its parent.
|
||||
open(name) {
|
||||
open(name, args) {
|
||||
const self = this;
|
||||
const popupName = `${name}Popup`;
|
||||
function clickFromPopup(evt) {
|
||||
return $(evt.target).closest('.js-pop-over').length !== 0;
|
||||
}
|
||||
/** opens the popup
|
||||
* @param evt the current event
|
||||
* @param options options (dataContextIfCurrentDataIsUndefined use this dataContext if this.currentData() is undefined)
|
||||
*/
|
||||
return function(evt, options) {
|
||||
// If a popup is already opened, clicking again on the opener element
|
||||
// should close it -- and interrupt the current `open` function.
|
||||
if (self.isOpen()) {
|
||||
const previousOpenerElement = self._getTopStack().openerElement;
|
||||
if (previousOpenerElement === evt.currentTarget) {
|
||||
self.close();
|
||||
return;
|
||||
} else {
|
||||
$(previousOpenerElement).removeClass('is-active');
|
||||
// Clean up previous popup content to prevent mixing
|
||||
self._cleanupPreviousPopupContent();
|
||||
}
|
||||
const popupName = `${name}Popup`;
|
||||
const openerElement = evt.target;
|
||||
let classicArgs = { openerElement: openerElement, name: popupName, title: self._getTitle(popupName), miscOptions: options };
|
||||
if (typeof(args) === "object") {
|
||||
classicArgs = Object.assign(classicArgs, args);
|
||||
}
|
||||
|
||||
// We determine the `openerElement` (the DOM element that is being clicked
|
||||
// and the one we take in reference to position the popup) from the event
|
||||
// if the popup has no parent, or from the parent `openerElement` if it
|
||||
// has one. This allows us to position a sub-popup exactly at the same
|
||||
// position than its parent.
|
||||
let openerElement;
|
||||
if (clickFromPopup(evt) && self._getTopStack()) {
|
||||
openerElement = self._getTopStack().openerElement;
|
||||
} else {
|
||||
// For Member Settings sub-popups, always start fresh to avoid content mixing
|
||||
if (popupName.includes('changeLanguage') || popupName.includes('changeAvatar') ||
|
||||
popupName.includes('editProfile') || popupName.includes('changePassword') ||
|
||||
popupName.includes('invitePeople') || popupName.includes('support')) {
|
||||
self._stack = [];
|
||||
}
|
||||
openerElement = evt.currentTarget;
|
||||
}
|
||||
$(openerElement).addClass('is-active');
|
||||
PopupComponent.open(classicArgs);
|
||||
evt.preventDefault();
|
||||
|
||||
// We push our popup data to the stack. The top of the stack is always
|
||||
// used as the data source for our current popup.
|
||||
self._stack.push({
|
||||
popupName,
|
||||
openerElement,
|
||||
hasPopupParent: clickFromPopup(evt),
|
||||
title: self._getTitle(popupName),
|
||||
depth: self._stack.length,
|
||||
offset: self._getOffset(openerElement),
|
||||
dataContext: (this && this.currentData && this.currentData()) || (options && options.dataContextIfCurrentDataIsUndefined) || this,
|
||||
});
|
||||
|
||||
const $contentWrapper = $('.content-wrapper')
|
||||
if ($contentWrapper.length > 0) {
|
||||
const contentWrapper = $contentWrapper[0];
|
||||
self._getTopStack().scrollTop = contentWrapper.scrollTop;
|
||||
// scroll from e.g. delete comment to the top (where the confirm button is)
|
||||
$contentWrapper.scrollTop(0);
|
||||
}
|
||||
|
||||
// If there are no popup currently opened we use the Blaze API to render
|
||||
// one into the DOM. We use a reactive function as the data parameter that
|
||||
// return the complete along with its top element and depends on our
|
||||
// internal dependency that is being invalidated every time the top
|
||||
// element of the stack has changed and we want to update the popup.
|
||||
//
|
||||
// Otherwise if there is already a popup open we just need to invalidate
|
||||
// our internal dependency, and since we just changed the top element of
|
||||
// our internal stack, the popup will be updated with the new data.
|
||||
if (!self.isOpen()) {
|
||||
if (!Template[popupName]) {
|
||||
console.error('Template not found:', popupName);
|
||||
return;
|
||||
}
|
||||
self.current = Blaze.renderWithData(
|
||||
self.template,
|
||||
() => {
|
||||
self._dep.depend();
|
||||
return { ...self._getTopStack(), stack: self._stack };
|
||||
},
|
||||
document.body,
|
||||
);
|
||||
} else {
|
||||
self._dep.changed();
|
||||
}
|
||||
// important so that one click does not opens multiple, stacked popups
|
||||
evt.stopPropagation();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -127,149 +31,40 @@ window.Popup = new (class {
|
|||
/// });
|
||||
afterConfirm(name, action) {
|
||||
const self = this;
|
||||
|
||||
return function(evt, tpl) {
|
||||
const context = (this.currentData && this.currentData()) || this;
|
||||
context.__afterConfirmAction = action;
|
||||
self.open(name).call(context, evt, tpl);
|
||||
tpl ??= {};
|
||||
tpl.afterConfirm = action;
|
||||
// Just a wrapper of open which will call `action` on some events
|
||||
// see PopupDetachedComponent; for now this is hardcoded
|
||||
self.open(name)(evt, tpl);
|
||||
evt.preventDefault();
|
||||
};
|
||||
}
|
||||
|
||||
/// The public reactive state of the popup.
|
||||
isOpen() {
|
||||
this._dep.changed();
|
||||
return Boolean(this.current);
|
||||
}
|
||||
|
||||
/// In case the popup was opened from a parent popup we can get back to it
|
||||
/// with this `Popup.back()` function. You can go back several steps at once
|
||||
/// by providing a number to this function, e.g. `Popup.back(2)`. In this case
|
||||
/// intermediate popup won't even be rendered on the DOM. If the number of
|
||||
/// steps back is greater than the popup stack size, the popup will be closed.
|
||||
back(n = 1) {
|
||||
if (this._stack.length > n) {
|
||||
const $contentWrapper = $('.content-wrapper')
|
||||
if ($contentWrapper.length > 0) {
|
||||
const contentWrapper = $contentWrapper[0];
|
||||
const stack = this._stack[this._stack.length - n];
|
||||
// scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax)
|
||||
const scrollTopMax = contentWrapper.scrollTopMax || contentWrapper.scrollHeight - contentWrapper.clientHeight;
|
||||
if (scrollTopMax && stack.scrollTop > scrollTopMax) {
|
||||
// sometimes scrollTopMax is lower than scrollTop, so i need this dirty hack
|
||||
setTimeout(() => {
|
||||
$contentWrapper.scrollTop(stack.scrollTop);
|
||||
}, 6);
|
||||
}
|
||||
// restore the old popup scroll position
|
||||
$contentWrapper.scrollTop(stack.scrollTop);
|
||||
}
|
||||
_.times(n, () => this._stack.pop());
|
||||
this._dep.changed();
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
_.times(n, () => PopupComponent.destroy());
|
||||
}
|
||||
|
||||
/// Close the current opened popup.
|
||||
close() {
|
||||
if (this.isOpen()) {
|
||||
Blaze.remove(this.current);
|
||||
this.current = null;
|
||||
|
||||
const openerElement = this._getTopStack().openerElement;
|
||||
$(openerElement).removeClass('is-active');
|
||||
|
||||
this._stack = [];
|
||||
// Clean up popup content when closing
|
||||
this._cleanupPreviousPopupContent();
|
||||
}
|
||||
this.back();
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
this.back(PopupComponent.stack.length)
|
||||
}
|
||||
|
||||
|
||||
getOpenerComponent(n=4) {
|
||||
const { openerElement } = Template.parentData(n);
|
||||
return BlazeComponent.getComponentForElement(openerElement);
|
||||
}
|
||||
|
||||
// An utility function that returns the top element of the internal stack
|
||||
_getTopStack() {
|
||||
return this._stack[this._stack.length - 1];
|
||||
}
|
||||
|
||||
_cleanupPreviousPopupContent() {
|
||||
// Force a re-render to ensure proper cleanup
|
||||
if (this._dep) {
|
||||
this._dep.changed();
|
||||
}
|
||||
}
|
||||
|
||||
// We automatically calculate the popup offset from the reference element
|
||||
// position and dimensions. We also reactively use the window dimensions to
|
||||
// ensure that the popup is always visible on the screen.
|
||||
_getOffset(element) {
|
||||
const $element = $(element);
|
||||
return () => {
|
||||
Utils.windowResizeDep.depend();
|
||||
|
||||
if (Utils.isMiniScreen()) return { left: 0, top: 0 };
|
||||
|
||||
// If the opener element is missing (e.g., programmatic open), fallback to viewport origin
|
||||
if (!$element || $element.length === 0) {
|
||||
return { left: 10, top: 10, maxHeight: $(window).height() - 20 };
|
||||
}
|
||||
|
||||
const offset = $element.offset();
|
||||
// Calculate actual popup width based on CSS: min(380px, 55vw)
|
||||
const viewportWidth = $(window).width();
|
||||
const viewportHeight = $(window).height();
|
||||
const popupWidth = Math.min(380, viewportWidth * 0.55) + 15; // Add 15px for margin
|
||||
|
||||
// Check if this is an admin panel edit popup
|
||||
const isAdminEditPopup = $element.hasClass('edit-user') ||
|
||||
$element.hasClass('edit-org') ||
|
||||
$element.hasClass('edit-team');
|
||||
|
||||
if (isAdminEditPopup) {
|
||||
// Center the popup horizontally and use full height
|
||||
const centeredLeft = (viewportWidth - popupWidth) / 2;
|
||||
|
||||
return {
|
||||
left: Math.max(10, centeredLeft), // Ensure popup doesn't go off screen
|
||||
top: 10, // Start from top with small margin
|
||||
maxHeight: viewportHeight - 20, // Use full height minus small margins
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate available height for popup
|
||||
const popupTop = offset.top + $element.outerHeight();
|
||||
|
||||
// For language popup, don't use dynamic height to avoid overlapping board
|
||||
const isLanguagePopup = $element.hasClass('js-change-language');
|
||||
let availableHeight, maxPopupHeight;
|
||||
|
||||
if (isLanguagePopup) {
|
||||
// For language popup, position content area below right vertical scrollbar
|
||||
const availableHeight = viewportHeight - popupTop - 20; // 20px margin from bottom (near scrollbar)
|
||||
const calculatedHeight = Math.min(availableHeight, viewportHeight * 0.5); // Max 50% of viewport
|
||||
|
||||
return {
|
||||
left: Math.min(offset.left, viewportWidth - popupWidth),
|
||||
top: popupTop,
|
||||
maxHeight: Math.max(calculatedHeight, 200), // Minimum 200px height
|
||||
};
|
||||
} else {
|
||||
// For other popups, use the dynamic height calculation
|
||||
availableHeight = viewportHeight - popupTop - 20; // 20px margin from bottom
|
||||
maxPopupHeight = Math.min(availableHeight, viewportHeight * 0.8); // Max 80% of viewport
|
||||
|
||||
return {
|
||||
left: Math.min(offset.left, viewportWidth - popupWidth),
|
||||
top: popupTop,
|
||||
maxHeight: Math.max(maxPopupHeight, 200), // Minimum 200px height
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// We get the title from the translation files. Instead of returning the
|
||||
// result, we return a function that compute the result and since `TAPi18n.__`
|
||||
// is a reactive data source, the title will be changed reactively.
|
||||
|
|
@ -297,10 +92,11 @@ escapeActions.forEach(actionName => {
|
|||
EscapeActions.register(
|
||||
`popup-${actionName}`,
|
||||
() => Popup[actionName](),
|
||||
() => Popup.isOpen(),
|
||||
() => PopupComponent.stack.length > 0,
|
||||
{
|
||||
noClickEscapeOn: '.js-pop-over,.js-open-card-title-popup,.js-open-inlined-form,.textcomplete-dropdown',
|
||||
// will maybe need something more robust, but for now it enables multiple cards opened without closing each other when clicking on common UI elements
|
||||
noClickEscapeOn: '.js-pop-over,.js-open-card-title-popup,.js-open-inlined-form,.textcomplete-dropdown,.js-card-details,.board-sidebar,#header,.add-comment-reaction',
|
||||
enabledOnClick: actionName === 'close',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue