mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
Resize height of swimlane by dragging. Font Awesome to Unicode icons.
Thanks to xet7 !
This commit is contained in:
parent
2947238a02
commit
09631d6b0c
16 changed files with 832 additions and 146 deletions
|
|
@ -192,7 +192,7 @@ and adds the following new features:
|
|||
|
||||
- [Mobile one board per row. Board zoom size percent. Board toggle mobile/desktop mode. In Progress](https://github.com/wekan/wekan/commit/752699d1c2fb8ea9ff0f3ec9ae0b2b776443d826).
|
||||
Thanks to xet7.
|
||||
- [Drag any files from file manager to minicard or opened card.
|
||||
- Drag any files from file manager to minicard or opened card.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/3e9481c5bd2c02ba501bd0a6ef1d1e6ce82bb1d9),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/cdd7d69c660d0b6ac06b7b75d4f59985b8a9322a).
|
||||
Thanks to xet7.
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ template(name="minicard")
|
|||
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
|
||||
if canModifyCard
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.fa.fa-navicon.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
| ↔️
|
||||
else
|
||||
a.fa.fa-navicon.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
a.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰
|
||||
.dates
|
||||
if getReceived
|
||||
unless getStart
|
||||
|
|
@ -36,7 +36,7 @@ template(name="minicard")
|
|||
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}}")
|
||||
|
|
@ -45,11 +45,11 @@ template(name="minicard")
|
|||
.upload-progress-fill(style="width: {{progress}}%")
|
||||
if $eq status 'error'
|
||||
.upload-progress-error
|
||||
i.fa.fa-exclamation-triangle
|
||||
| ⚠️
|
||||
span {{_ 'upload-failed'}}
|
||||
else if $eq status 'completed'
|
||||
.upload-progress-success
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
span {{_ 'upload-completed'}}
|
||||
|
||||
.minicard-title
|
||||
|
|
@ -61,12 +61,12 @@ template(name="minicard")
|
|||
| {{ parentCardName }}
|
||||
if isLinkedBoard
|
||||
a.js-linked-link
|
||||
span.linked-icon.fa.fa-folder
|
||||
span.linked-icon | 📁
|
||||
else if isLinkedCard
|
||||
a.js-linked-link
|
||||
span.linked-icon.fa.fa-id-card
|
||||
span.linked-icon | 🃏
|
||||
if getArchived
|
||||
span.linked-icon.linked-archived.fa.fa-archive
|
||||
span.linked-icon.linked-archived | 📦
|
||||
+viewer
|
||||
if currentBoard.allowsCardNumber
|
||||
span.card-number
|
||||
|
|
@ -147,7 +147,7 @@ template(name="minicard")
|
|||
if canModifyCard
|
||||
if comments.length
|
||||
.badge(title="{{_ 'card-comments-title' comments.length }}")
|
||||
span.badge-icon.fa.fa-comment-o.badge-comment.badge-text
|
||||
span.badge-icon.badge-comment.badge-text | 💬
|
||||
= ' '
|
||||
= comments.length
|
||||
//span.badge-comment.badge-text
|
||||
|
|
@ -155,36 +155,36 @@ template(name="minicard")
|
|||
if getDescription
|
||||
unless currentBoard.allowsDescriptionTextOnMinicard
|
||||
.badge.badge-state-image-only(title=getDescription)
|
||||
span.badge-icon.fa.fa-align-left
|
||||
span.badge-icon | 📝
|
||||
if getVoteQuestion
|
||||
.badge.badge-state-image-only(title=getVoteQuestion)
|
||||
span.badge-icon.fa.fa-thumbs-up(class="{{#if voteState}}text-green{{/if}}")
|
||||
span.badge-icon(class="{{#if voteState}}text-green{{/if}}") | 👍
|
||||
span.badge-text {{ voteCountPositive }}
|
||||
span.badge-icon.fa.fa-thumbs-down(class="{{#if $eq voteState false}}text-red{{/if}}")
|
||||
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}") | 👎
|
||||
span.badge-text {{ voteCountNegative }}
|
||||
if getPokerQuestion
|
||||
.badge.badge-state-image-only(title=getPokerQuestion)
|
||||
span.badge-icon.fa.fa-check(class="{{#if pokerState}}text-green{{/if}}")
|
||||
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}") | ✅
|
||||
if expiredPoker
|
||||
span.badge-text {{ getPokerEstimation }}
|
||||
if attachments.length
|
||||
if currentBoard.allowsBadgeAttachmentOnMinicard
|
||||
.badge
|
||||
span.badge-icon.fa.fa-paperclip
|
||||
span.badge-icon | 📎
|
||||
span.badge-text= attachments.length
|
||||
if checklists.length
|
||||
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
|
||||
span.badge-icon.fa.fa-check-square-o
|
||||
span.badge-icon | ☑️
|
||||
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
|
||||
if allSubtasks.count
|
||||
.badge
|
||||
span.badge-icon.fa.fa-sitemap
|
||||
span.badge-icon | 🌐
|
||||
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.fa.fa-sort
|
||||
span.badge-icon | 🔢
|
||||
span.badge-text.check-list-sort {{ sort }}
|
||||
if currentBoard.allowsDescriptionTextOnMinicard
|
||||
if getDescription
|
||||
|
|
@ -193,7 +193,7 @@ template(name="minicard")
|
|||
| {{ getDescription }}
|
||||
if shouldShowListOnMinicard
|
||||
.minicard-list-name
|
||||
i.fa.fa-list
|
||||
| 📋
|
||||
| {{ listName }}
|
||||
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
|
||||
.parent-subtext
|
||||
|
|
@ -212,50 +212,50 @@ template(name="minicardDetailsActionsPopup")
|
|||
if canModifyCard
|
||||
li
|
||||
a.js-move-card
|
||||
i.fa.fa-arrow-right
|
||||
| ➡️
|
||||
| {{_ 'moveCardPopup-title'}}
|
||||
li
|
||||
a.js-copy-card
|
||||
i.fa.fa-copy
|
||||
| 📋
|
||||
| {{_ 'copyCardPopup-title'}}
|
||||
hr
|
||||
li
|
||||
a.js-archive
|
||||
i.fa.fa-arrow-right
|
||||
i.fa.fa-archive
|
||||
| ➡️
|
||||
| 📦
|
||||
| {{_ 'archive-card'}}
|
||||
hr
|
||||
li
|
||||
a.js-move-card-to-top
|
||||
i.fa.fa-arrow-up
|
||||
| ⬆️
|
||||
| {{_ 'moveCardToTop-title'}}
|
||||
li
|
||||
a.js-move-card-to-bottom
|
||||
i.fa.fa-arrow-down
|
||||
| ⬇️
|
||||
| {{_ 'moveCardToBottom-title'}}
|
||||
hr
|
||||
li
|
||||
a.js-add-labels
|
||||
i.fa.fa-tags
|
||||
| 🏷️
|
||||
| {{_ 'card-edit-labels'}}
|
||||
li
|
||||
a.js-due-date
|
||||
i.fa.fa-sign-in
|
||||
| 📥
|
||||
| {{_ 'editCardDueDatePopup-title'}}
|
||||
li
|
||||
a.js-set-card-color
|
||||
i.fa.fa-paint-brush
|
||||
| 🎨
|
||||
| {{_ 'setCardColorPopup-title'}}
|
||||
li
|
||||
a.js-link
|
||||
i.fa.fa-link
|
||||
| 🔗
|
||||
| {{_ 'link-card'}}
|
||||
li
|
||||
a.js-toggle-watch-card
|
||||
if isWatching
|
||||
i.fa.fa-eye
|
||||
| 👁️
|
||||
| {{_ 'unwatch'}}
|
||||
else
|
||||
i.fa.fa-eye-slash
|
||||
| 👁️-slash
|
||||
| {{_ 'watch'}}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
background: transparent;
|
||||
transition: background-color 0.2s ease;
|
||||
border-radius: 2px;
|
||||
/* Ensure the handle is clickable */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.list-resize-handle:hover {
|
||||
|
|
@ -90,6 +92,8 @@
|
|||
width: var(--list-width, auto) !important;
|
||||
min-width: var(--list-width, auto) !important;
|
||||
max-width: var(--list-width, auto) !important;
|
||||
/* Ensure the width is applied immediately */
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
body.list-resizing-active {
|
||||
|
|
@ -250,7 +254,70 @@ body.list-resizing-active * {
|
|||
}
|
||||
.list.list-collapsed {
|
||||
flex: none;
|
||||
min-width: 60px;
|
||||
max-width: 80px;
|
||||
width: 60px;
|
||||
min-height: 60vh;
|
||||
height: 60vh;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
.list.list-collapsed .list-header {
|
||||
padding: 1vh 1.5vw 0.5vh;
|
||||
min-height: 2.5vh !important;
|
||||
height: auto !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
overflow: visible !important;
|
||||
width: 100%;
|
||||
max-width: 60px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.list.list-collapsed .list-header .js-collapse {
|
||||
margin: 0 auto 20px auto;
|
||||
z-index: 10;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
width: fit-content;
|
||||
}
|
||||
.list.list-collapsed .list-header .list-rotated {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
margin: 20px 0 0 0 !important;
|
||||
position: relative !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||
text-align: left;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
display: block !important;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
color: #333;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px 4px;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
width: 25vh;
|
||||
height: 60vh;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||
z-index: 10;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.list.list-composer .open-list-composer,
|
||||
.list .list-composer .open-list-composer {
|
||||
color: #8c8c8c;
|
||||
|
|
@ -334,11 +401,152 @@ body.list-resizing-active * {
|
|||
color: #a6a6a6;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.list-header .list-header-uncollapse-left {
|
||||
.list-header .js-collapse {
|
||||
color: #a6a6a6;
|
||||
margin-right: 15px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.list-header .list-header-uncollapse-right {
|
||||
color: #a6a6a6;
|
||||
.list-header .js-collapse:hover {
|
||||
background-color: #e0e0e0;
|
||||
color: #333;
|
||||
}
|
||||
.list.list-collapsed .list-header .js-collapse {
|
||||
display: inline-block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for collapsed lists */
|
||||
@media (min-width: 768px) {
|
||||
.list.list-collapsed {
|
||||
min-width: 60px;
|
||||
max-width: 80px;
|
||||
width: 60px;
|
||||
min-height: 60vh;
|
||||
height: 60vh;
|
||||
}
|
||||
.list.list-collapsed .list-header {
|
||||
max-width: 60px;
|
||||
margin: 0 auto;
|
||||
min-height: 2.5vh !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.list.list-collapsed .list-header .list-rotated {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
margin: 20px 0 0 0 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||
width: 15vh;
|
||||
font-size: 12px;
|
||||
height: 30px;
|
||||
line-height: 1.2;
|
||||
padding: 8px 4px;
|
||||
margin: 0 auto;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||
text-align: left;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
display: block !important;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid #ddd;
|
||||
color: #333;
|
||||
z-index: 10;
|
||||
}
|
||||
.list.list-collapsed .list-header .js-collapse {
|
||||
margin: 0 auto 20px auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.list.list-collapsed {
|
||||
min-height: 60vh;
|
||||
height: 60vh;
|
||||
}
|
||||
.list.list-collapsed .list-header {
|
||||
min-height: 2.5vh !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.list.list-collapsed .list-header .list-rotated {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
margin: 20px 0 0 0 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||
width: 15vh;
|
||||
font-size: 12px;
|
||||
height: 30px;
|
||||
line-height: 1.2;
|
||||
padding: 8px 4px;
|
||||
margin: 0 auto;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||
text-align: left;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
display: block !important;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid #ddd;
|
||||
color: #333;
|
||||
z-index: 10;
|
||||
}
|
||||
.list.list-collapsed .list-header .js-collapse {
|
||||
margin: 0 auto 20px auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.list.list-collapsed {
|
||||
min-height: 60vh;
|
||||
height: 60vh;
|
||||
}
|
||||
.list.list-collapsed .list-header {
|
||||
min-height: 2.5vh !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.list.list-collapsed .list-header .list-rotated {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
margin: 20px 0 0 0 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||
width: 15vh;
|
||||
font-size: 12px;
|
||||
height: 30px;
|
||||
line-height: 1.2;
|
||||
padding: 8px 4px;
|
||||
margin: 0 auto;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||
text-align: left;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
display: block !important;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid #ddd;
|
||||
color: #333;
|
||||
z-index: 10;
|
||||
}
|
||||
.list.list-collapsed .list-header .js-collapse {
|
||||
margin: 0 auto 20px auto;
|
||||
}
|
||||
}
|
||||
.list-header .list-header-collapse {
|
||||
color: #a6a6a6;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ template(name='list')
|
|||
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
+listHeader
|
||||
+listBody
|
||||
.list-resize-handle.js-list-resize-handle
|
||||
.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}}")
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ BlazeComponent.extendComponent({
|
|||
onRendered() {
|
||||
const boardComponent = this.parentComponent().parentComponent();
|
||||
|
||||
// Initialize list resize functionality
|
||||
// Initialize list resize functionality immediately
|
||||
this.initializeListResize();
|
||||
|
||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
|
|
@ -201,13 +201,15 @@ BlazeComponent.extendComponent({
|
|||
listWidth() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
return user.getListWidth(list.boardId, list._id);
|
||||
if (!user || !list) return 270; // Return default width if user or list is not available
|
||||
return user.getListWidthFromStorage(list.boardId, list._id);
|
||||
},
|
||||
|
||||
listConstraint() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
return user.getListConstraint(list.boardId, list._id);
|
||||
if (!user || !list) return 550; // Return default constraint if user or list is not available
|
||||
return user.getListConstraintFromStorage(list.boardId, list._id);
|
||||
},
|
||||
|
||||
autoWidth() {
|
||||
|
|
@ -217,12 +219,31 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
initializeListResize() {
|
||||
// Check if we're still in a valid template context
|
||||
if (!Template.currentData()) {
|
||||
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');
|
||||
Meteor.setTimeout(() => {
|
||||
if (!this.isDestroyed) {
|
||||
this.initializeListResize();
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Only enable resize for non-collapsed, non-auto-width lists
|
||||
if (list.collapsed || this.autoWidth()) {
|
||||
const isAutoWidth = this.autoWidth();
|
||||
if (list.collapsed || isAutoWidth) {
|
||||
$resizeHandle.hide();
|
||||
return;
|
||||
}
|
||||
|
|
@ -240,41 +261,41 @@ BlazeComponent.extendComponent({
|
|||
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');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const doResize = (e) => {
|
||||
if (!isResizing) return;
|
||||
if (!isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
const deltaX = currentX - startX;
|
||||
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
|
||||
|
||||
// Apply the new width immediately for real-time feedback using CSS custom properties
|
||||
// Apply the new width immediately for real-time feedback
|
||||
$list[0].style.setProperty('--list-width', `${newWidth}px`);
|
||||
$list.css({
|
||||
'width': `${newWidth}px`,
|
||||
'min-width': `${newWidth}px`,
|
||||
'max-width': `${newWidth}px`,
|
||||
'flex': 'none',
|
||||
'flex-basis': 'auto',
|
||||
'flex-grow': '0',
|
||||
'flex-shrink': '0'
|
||||
});
|
||||
$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');
|
||||
|
||||
// Debug: log the width change
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Resizing list to ${newWidth}px`);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const stopResize = (e) => {
|
||||
|
|
@ -287,17 +308,15 @@ BlazeComponent.extendComponent({
|
|||
const deltaX = currentX - startX;
|
||||
const finalWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
|
||||
|
||||
// Ensure the final width is applied using CSS custom properties
|
||||
// Ensure the final width is applied
|
||||
$list[0].style.setProperty('--list-width', `${finalWidth}px`);
|
||||
$list.css({
|
||||
'width': `${finalWidth}px`,
|
||||
'min-width': `${finalWidth}px`,
|
||||
'max-width': `${finalWidth}px`,
|
||||
'flex': 'none',
|
||||
'flex-basis': 'auto',
|
||||
'flex-grow': '0',
|
||||
'flex-shrink': '0'
|
||||
});
|
||||
$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');
|
||||
|
|
@ -311,16 +330,14 @@ BlazeComponent.extendComponent({
|
|||
const boardId = list.boardId;
|
||||
const listId = list._id;
|
||||
|
||||
// Use the same method as the hamburger menu
|
||||
// Use the new storage method that handles both logged-in and non-logged-in users
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Saving list width: ${finalWidth}px for list ${listId}`);
|
||||
}
|
||||
Meteor.call('applyListWidth', boardId, listId, finalWidth, listConstraint, (error, result) => {
|
||||
Meteor.call('applyListWidthToStorage', boardId, listId, finalWidth, listConstraint, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Error saving list width:', error);
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('List width saved successfully:', result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -334,9 +351,16 @@ BlazeComponent.extendComponent({
|
|||
$(document).on('mouseup', stopResize);
|
||||
|
||||
// Touch events for mobile
|
||||
$resizeHandle.on('touchstart', startResize);
|
||||
$(document).on('touchmove', doResize);
|
||||
$(document).on('touchend', stopResize);
|
||||
$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 changes
|
||||
component.autorun(() => {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ template(name="listBody")
|
|||
+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
|
||||
| ➕
|
||||
|
||||
template(name="spinnerList")
|
||||
.sk-spinner.sk-spinner-list(
|
||||
|
|
@ -54,7 +54,7 @@ template(name="addCardForm")
|
|||
|
||||
.add-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'add'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
a.js-close-inlined-form | ❌
|
||||
.add-controls.clearfix
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ template(name="listHeader")
|
|||
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
|
||||
else
|
||||
if collapsed
|
||||
a.js-collapse(title="{{_ 'uncollapse'}}")
|
||||
i.fa.fa-arrow-left.list-header-uncollapse-left
|
||||
i.fa.fa-arrow-right.list-header-uncollapse-right
|
||||
if showCardsCountForList cards.length
|
||||
br
|
||||
span.cardCount {{cardsCount}}
|
||||
|
|
@ -29,6 +26,10 @@ template(name="listHeader")
|
|||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
else
|
||||
if collapsed
|
||||
a.js-collapse(title="{{_ 'uncollapse'}}")
|
||||
| ⬅️
|
||||
| ➡️
|
||||
div(class="{{#if collapsed}}list-rotated{{/if}}")
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
|
|
@ -45,32 +46,32 @@ template(name="listHeader")
|
|||
if isMiniScreen
|
||||
if currentList
|
||||
if isWatching
|
||||
i.list-header-watch-icon.fa.fa-eye
|
||||
i.list-header-watch-icon | 👁️
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
|
||||
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
|
||||
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") | ➕
|
||||
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") | ☰
|
||||
else
|
||||
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
|
||||
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
a.list-header-menu-icon.js-select-list | ▶️
|
||||
a.list-header-handle.handle.js-list-handle | ↔️
|
||||
else if currentUser.isBoardMember
|
||||
if isWatching
|
||||
i.list-header-watch-icon.fa.fa-eye
|
||||
i.list-header-watch-icon | 👁️
|
||||
unless collapsed
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
//if isBoardAdmin
|
||||
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
|
||||
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") | ➕
|
||||
a.js-collapse(title="{{_ 'collapse'}}")
|
||||
i.fa.fa-arrow-right.list-header-collapse-right
|
||||
i.fa.fa-arrow-left.list-header-collapse-left
|
||||
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
|
||||
| ⬅️
|
||||
| ➡️
|
||||
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") | ☰
|
||||
if currentUser.isBoardAdmin
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
a.list-header-handle.handle.js-list-handle | ↔️
|
||||
|
||||
template(name="editListTitleForm")
|
||||
.list-composer
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ template(name="sidebar")
|
|||
.sidebar-actions
|
||||
.sidebar-shortcuts
|
||||
a.sidebar-btn.js-shortcuts(title="{{_ 'keyboard-shortcuts' }}")
|
||||
i.fa.fa-keyboard-o
|
||||
| ⌨️
|
||||
span {{_ 'keyboard-shortcuts' }}
|
||||
a.sidebar-btn.js-keyboard-shortcuts-toggle(
|
||||
title="{{#if isKeyboardShortcuts}}{{_ 'keyboard-shortcuts-enabled'}}{{else}}{{_ 'keyboard-shortcuts-disabled'}}{{/if}}")
|
||||
i.fa(class="fa-solid fa-{{#if isKeyboardShortcuts}}check-square-o{{else}}ban{{/if}}")
|
||||
| {{#if isKeyboardShortcuts}}✅{{else}}🚫{{/if}}
|
||||
if isAccessibilityEnabled
|
||||
a.sidebar-accessibility
|
||||
i.fa.fa-universal-access
|
||||
| ♿
|
||||
span {{_ 'accessibility'}}
|
||||
a.sidebar-xmark.js-close-sidebar ✕
|
||||
.sidebar-content.js-board-sidebar-content
|
||||
|
|
@ -22,7 +22,7 @@ template(name="sidebar")
|
|||
// i.fa.fa-navicon
|
||||
unless isDefaultView
|
||||
h2
|
||||
a.fa.fa-chevron-left.js-back-home
|
||||
a.js-back-home | ⬅️
|
||||
= getViewTitle
|
||||
if isOpen
|
||||
+Template.dynamic(template=getViewTemplate)
|
||||
|
|
@ -51,7 +51,7 @@ template(name='homeSidebar')
|
|||
hr
|
||||
unless currentUser.isNoComments
|
||||
h3.activity-title
|
||||
i.fa.fa-comments-o
|
||||
| 💬
|
||||
| {{_ 'activities'}}
|
||||
|
||||
.material-toggle-switch(title="{{_ 'show-activities'}}")
|
||||
|
|
@ -67,11 +67,11 @@ template(name="membersWidget")
|
|||
unless currentUser.isWorker
|
||||
h3
|
||||
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
|
||||
i.board-header-btn-icon.fa.fa-cog
|
||||
| ⚙️
|
||||
| {{_ 'boardMenuPopup-title'}}
|
||||
hr
|
||||
h3
|
||||
i.fa.fa-users
|
||||
| 👥
|
||||
| {{_ 'members'}}
|
||||
+basicTabs(tabs=tabs)
|
||||
+tabContent(slug="people")
|
||||
|
|
@ -83,15 +83,15 @@ template(name="membersWidget")
|
|||
if isSandstorm
|
||||
if currentUser.isBoardMember
|
||||
a.member.add-member.sandstorm-powerbox-request-identity(title="{{_ 'add-members'}}")
|
||||
i.fa.fa-plus
|
||||
| ➕
|
||||
else if currentUser.isBoardAdmin
|
||||
a.member.add-member.js-manage-board-members(title="{{_ 'add-members'}}")
|
||||
i.fa.fa-plus
|
||||
| ➕
|
||||
.clearfix
|
||||
if isInvited
|
||||
hr
|
||||
p
|
||||
i.fa.fa-exclamation-circle
|
||||
| ⚠️
|
||||
| {{_ 'just-invited'}}
|
||||
button.js-member-invite-accept.primary {{_ 'accept'}}
|
||||
button.js-member-invite-decline {{_ 'decline'}}
|
||||
|
|
@ -127,7 +127,7 @@ template(name="boardOrgGeneral")
|
|||
th
|
||||
if currentUser.isBoardAdmin
|
||||
a.member.orgOrTeamMember.add-member.js-manage-board-addOrg(title="{{_ 'add-members'}}")
|
||||
i.addTeamFaPlus.fa.fa-plus
|
||||
| ➕
|
||||
.divaddfaplusminus
|
||||
| {{_ 'add'}}
|
||||
each org in currentBoard.activeOrgs
|
||||
|
|
@ -148,7 +148,7 @@ template(name="boardTeamGeneral")
|
|||
th
|
||||
if currentUser.isBoardAdmin
|
||||
a.member.orgOrTeamMember.add-member.js-manage-board-addTeam(title="{{_ 'add-members'}}")
|
||||
i.addTeamFaPlus.fa.fa-plus
|
||||
| ➕
|
||||
.divaddfaplusminus
|
||||
| {{_ 'add'}}
|
||||
each currentBoard.activeTeams
|
||||
|
|
@ -161,7 +161,7 @@ template(name="boardChangeColorPopup")
|
|||
span.background-box(class="board-color-{{this}}")
|
||||
span {{this}}
|
||||
if isSelected
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
|
||||
template(name="boardChangeBackgroundImagePopup")
|
||||
form
|
||||
|
|
@ -423,7 +423,7 @@ template(name="chooseBoardSource")
|
|||
template(name="archiveBoardPopup")
|
||||
p {{_ 'close-board-pop'}}
|
||||
button.js-confirm.negate.full(type="submit")
|
||||
i.fa.fa-archive
|
||||
| 📦
|
||||
| {{_ 'archive'}}
|
||||
|
||||
template(name="outgoingWebhooksPopup")
|
||||
|
|
@ -459,25 +459,25 @@ template(name="boardMenuPopup")
|
|||
if currentUser.isBoardAdmin
|
||||
li
|
||||
a.js-open-rules-view(title="{{_ 'rules'}}")
|
||||
i.fa.fa-magic
|
||||
| ✨
|
||||
| {{_ 'rules'}}
|
||||
if currentUser.isBoardAdmin
|
||||
li
|
||||
a.js-custom-fields
|
||||
i.fa.fa-list-alt
|
||||
| 📝
|
||||
| {{_ 'custom-fields'}}
|
||||
li
|
||||
a.js-open-archives
|
||||
i.fa.fa-archive
|
||||
| 📦
|
||||
| {{_ 'archived-items'}}
|
||||
if currentUser.isBoardAdmin
|
||||
li
|
||||
a.js-change-board-color
|
||||
i.fa.fa-paint-brush
|
||||
| 🎨
|
||||
| {{_ 'board-change-color'}}
|
||||
li
|
||||
a.js-change-background-image
|
||||
i.fa.fa-picture-o
|
||||
| 🖼️
|
||||
| {{_ 'board-change-background-image'}}
|
||||
//Bug Board icons random dance https://github.com/wekan/wekan/issues/4214
|
||||
//if currentUser.isBoardAdmin
|
||||
|
|
@ -492,20 +492,20 @@ template(name="boardMenuPopup")
|
|||
if withApi
|
||||
li
|
||||
a.js-export-board
|
||||
i.fa.fa-share-alt
|
||||
| 📤
|
||||
| {{_ 'export-board'}}
|
||||
if currentUser.isBoardAdmin
|
||||
li
|
||||
a.js-outgoing-webhooks
|
||||
i.fa.fa-globe
|
||||
| 🌐
|
||||
| {{_ 'outgoing-webhooks'}}
|
||||
li
|
||||
a.js-card-settings
|
||||
i.fa.fa-id-card-o
|
||||
| 🃏
|
||||
| {{_ 'card-settings'}}
|
||||
li
|
||||
a.js-subtask-settings
|
||||
i.fa.fa-sitemap
|
||||
| 🌐
|
||||
| {{_ 'subtask-settings'}}
|
||||
unless currentBoard.isTemplatesBoard
|
||||
if currentUser.isBoardAdmin
|
||||
|
|
@ -513,41 +513,40 @@ template(name="boardMenuPopup")
|
|||
ul.pop-over-list
|
||||
li
|
||||
a.js-archive-board
|
||||
i.fa.fa-arrow-right
|
||||
i.fa.fa-archive
|
||||
| ➡️📦
|
||||
| {{_ 'archive-board'}}
|
||||
|
||||
template(name="exportBoard")
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.download-json-link(href="{{exportUrl}}", download="{{exportJsonFilename}}")
|
||||
i.fa.fa-share-alt
|
||||
| 📤
|
||||
| {{_ 'export-board-json'}}
|
||||
li
|
||||
a(href="{{exportUrlExcel}}", download="{{exportFilenameExcel}}")
|
||||
i.fa.fa-share-alt
|
||||
| 📤
|
||||
| {{_ 'export-board-excel'}}
|
||||
li
|
||||
a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
|
||||
i.fa.fa-share-alt
|
||||
| 📤
|
||||
| {{_ 'export-board-csv'}} ,
|
||||
li
|
||||
a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
|
||||
i.fa.fa-share-alt
|
||||
| 📤
|
||||
| {{_ 'export-board-csv'}} ;
|
||||
li
|
||||
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
|
||||
i.fa.fa-share-alt
|
||||
| 📤
|
||||
| {{_ 'export-board-tsv'}}
|
||||
li
|
||||
a.html-export-board
|
||||
i.fa.fa-archive
|
||||
| 📦
|
||||
| {{_ 'export-board-html'}}
|
||||
|
||||
template(name="labelsWidget")
|
||||
.board-widget.board-widget-labels
|
||||
h3
|
||||
i.fa.fa-tags
|
||||
| 🏷️
|
||||
| {{_ 'labels'}}
|
||||
.board-widget-content
|
||||
each currentBoard.labels
|
||||
|
|
@ -558,7 +557,7 @@ template(name="labelsWidget")
|
|||
= name
|
||||
if currentUser.isBoardAdmin
|
||||
a.card-label.add-label.js-add-label(title="{{_ 'label-create'}}")
|
||||
i.fa.fa-plus
|
||||
| ➕
|
||||
|
||||
template(name="memberPopup")
|
||||
.board-member-menu
|
||||
|
|
@ -570,7 +569,7 @@ template(name="memberPopup")
|
|||
p.quiet @#{user.username}
|
||||
if isInvited
|
||||
p
|
||||
i.fa.fa-exclamation-circle
|
||||
| ⚠️
|
||||
| {{_ 'not-accepted-yet'}}
|
||||
|
||||
ul.pop-over-list
|
||||
|
|
@ -665,31 +664,31 @@ template(name="changePermissionsPopup")
|
|||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-admin{{/if}}")
|
||||
| {{_ 'admin'}}
|
||||
if isAdmin
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
span.sub-name {{_ 'admin-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-normal{{/if}}")
|
||||
| {{_ 'normal'}}
|
||||
if isNormal
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
span.sub-name {{_ 'normal-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-no-comments{{/if}}")
|
||||
| {{_ 'no-comments'}}
|
||||
if isNoComments
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
span.sub-name {{_ 'no-comments-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-only{{/if}}")
|
||||
| {{_ 'comment-only'}}
|
||||
if isCommentOnly
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
span.sub-name {{_ 'comment-only-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
|
||||
| {{_ 'worker'}}
|
||||
if isWorker
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
span.sub-name {{_ 'worker-desc'}}
|
||||
if isLastAdmin
|
||||
hr
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ template(name="swimlaneFixedHeader")
|
|||
| {{isTitleDefault title}}
|
||||
.swimlane-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
a.js-open-add-swimlane-menu.swimlane-header-plus-icon
|
||||
| ➕(title="{{_ 'add-swimlane'}}")
|
||||
a.js-open-swimlane-menu
|
||||
| ☰(title="{{_ 'swimlaneActionPopup-title'}}")
|
||||
a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
|
||||
| ➕
|
||||
a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
|
||||
| ☰
|
||||
//// TODO: Collapse Swimlane: make button working, etc.
|
||||
//unless collapsed
|
||||
// a.js-collapse-swimlane(title="{{_ 'collapse'}}")
|
||||
|
|
@ -99,7 +99,7 @@ template(name="setSwimlaneColorPopup")
|
|||
each colors
|
||||
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
|
||||
button.primary.confirm.js-submit(style="margin-left:0") {{_ 'save'}}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
flex-direction: row;
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-collapse-down {
|
||||
font-size: 50%;
|
||||
|
|
@ -234,3 +235,106 @@
|
|||
background: #4b0082 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Swimlane resize handle */
|
||||
.swimlane-resize-handle {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 8px;
|
||||
background: transparent;
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
/* Add a subtle indicator line */
|
||||
.swimlane-resize-handle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 2px;
|
||||
background: rgba(0, 123, 255, 0.6);
|
||||
border-radius: 1px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
body.swimlane-resizing-active {
|
||||
cursor: row-resize !important;
|
||||
}
|
||||
|
||||
body.swimlane-resizing-active * {
|
||||
cursor: row-resize !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ template(name="swimlane")
|
|||
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)
|
||||
|
|
@ -67,7 +68,7 @@ template(name="addListForm")
|
|||
a.js-list-template {{_ 'template'}}
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form(title="{{_ 'add-list'}}")
|
||||
i.fa.fa-plus
|
||||
| ➕
|
||||
|
||||
template(name="moveSwimlanePopup")
|
||||
unless currentUser.isWorker
|
||||
|
|
|
|||
|
|
@ -247,10 +247,21 @@ BlazeComponent.extendComponent({
|
|||
Utils.isTouchScreenOrShowDesktopDragHandles()
|
||||
? ['.js-list-handle', '.js-swimlane-header-handle']
|
||||
: ['.js-list-header'],
|
||||
);
|
||||
).concat([
|
||||
'.js-list-resize-handle',
|
||||
'.js-swimlane-resize-handle'
|
||||
]);
|
||||
|
||||
const isResizeHandle = $(evt.target).closest('.js-list-resize-handle, .js-swimlane-resize-handle').length > 0;
|
||||
const isInNoDragArea = $(evt.target).closest(noDragInside.join(',')).length > 0;
|
||||
|
||||
if (isResizeHandle) {
|
||||
console.log('Board drag prevented - resize handle clicked');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$(evt.target).closest(noDragInside.join(',')).length === 0 &&
|
||||
!isInNoDragArea &&
|
||||
this.$('.swimlane').prop('clientHeight') > evt.offsetY
|
||||
) {
|
||||
this._isDragging = true;
|
||||
|
|
@ -283,9 +294,150 @@ BlazeComponent.extendComponent({
|
|||
swimlaneHeight() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const swimlane = Template.currentData();
|
||||
const height = user.getSwimlaneHeight(swimlane.boardId, swimlane._id);
|
||||
const height = user.getSwimlaneHeightFromStorage(swimlane.boardId, swimlane._id);
|
||||
return height == -1 ? "auto" : (height + 5 + "px");
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
// Initialize swimlane resize functionality immediately
|
||||
this.initializeSwimlaneResize();
|
||||
},
|
||||
|
||||
initializeSwimlaneResize() {
|
||||
// Check if we're still in a valid template context
|
||||
if (!Template.currentData()) {
|
||||
console.warn('No current template data available for swimlane resize initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
const swimlane = Template.currentData();
|
||||
const $swimlane = $(`#swimlane-${swimlane._id}`);
|
||||
const $resizeHandle = $swimlane.find('.js-swimlane-resize-handle');
|
||||
|
||||
// Check if elements exist
|
||||
if (!$swimlane.length || !$resizeHandle.length) {
|
||||
console.warn('Swimlane or resize handle not found, retrying in 100ms');
|
||||
Meteor.setTimeout(() => {
|
||||
if (!this.isDestroyed) {
|
||||
this.initializeSwimlaneResize();
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ($resizeHandle.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isResizing = false;
|
||||
let startY = 0;
|
||||
let startHeight = 0;
|
||||
const minHeight = 100;
|
||||
const maxHeight = 2000;
|
||||
|
||||
const startResize = (e) => {
|
||||
isResizing = true;
|
||||
startY = e.pageY || e.originalEvent.touches[0].pageY;
|
||||
startHeight = parseInt($swimlane.css('height')) || 300;
|
||||
|
||||
|
||||
$swimlane.addClass('swimlane-resizing');
|
||||
$('body').addClass('swimlane-resizing-active');
|
||||
$('body').css('user-select', 'none');
|
||||
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const doResize = (e) => {
|
||||
if (!isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = e.pageY || e.originalEvent.touches[0].pageY;
|
||||
const deltaY = currentY - startY;
|
||||
const newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
|
||||
|
||||
|
||||
// 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');
|
||||
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const stopResize = (e) => {
|
||||
if (!isResizing) return;
|
||||
|
||||
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
|
||||
$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;
|
||||
const swimlaneId = swimlane._id;
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
}
|
||||
|
||||
// Use the new storage method that handles both logged-in and non-logged-in users
|
||||
Meteor.call('applySwimlaneHeightToStorage', boardId, swimlaneId, finalHeight, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Error saving swimlane height:', error);
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
},
|
||||
}).register('swimlane');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ template(name="changeLanguagePopup")
|
|||
a.js-set-language
|
||||
= name
|
||||
if isCurrentLanguage
|
||||
i.fa.fa-check
|
||||
| ✅
|
||||
|
||||
template(name="changeSettingsPopup")
|
||||
ul.pop-over-list
|
||||
|
|
@ -186,7 +186,7 @@ template(name="changeSettingsPopup")
|
|||
unless currentUser.isWorker
|
||||
li
|
||||
label.bold.clear
|
||||
i.fa.fa-sort-numeric-asc
|
||||
| 🔢
|
||||
| {{_ 'show-cards-minimum-count'}}
|
||||
input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="-1")
|
||||
label.bold.clear
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ Blaze.registerHelper('isTouchScreenOrShowDesktopDragHandles', () =>
|
|||
|
||||
Blaze.registerHelper('moment', (...args) => {
|
||||
args.pop(); // hash
|
||||
const [date, format] = args;
|
||||
return format(new Date(date), format ?? 'LLLL');
|
||||
const [date, formatStr] = args;
|
||||
return format(new Date(date), formatStr ?? 'LLLL');
|
||||
});
|
||||
|
||||
Blaze.registerHelper('canModifyCard', () =>
|
||||
|
|
|
|||
202
models/users.js
202
models/users.js
|
|
@ -875,6 +875,52 @@ Users.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
getSwimlaneHeightFromStorage(boardId, swimlaneId) {
|
||||
// For logged-in users, get from profile
|
||||
if (this._id) {
|
||||
return this.getSwimlaneHeight(boardId, swimlaneId);
|
||||
}
|
||||
|
||||
// For non-logged-in users, get from localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-swimlane-heights');
|
||||
if (stored) {
|
||||
const heights = JSON.parse(stored);
|
||||
if (heights[boardId] && heights[boardId][swimlaneId]) {
|
||||
return heights[boardId][swimlaneId];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading swimlane heights from localStorage:', e);
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
|
||||
setSwimlaneHeightToStorage(boardId, swimlaneId, height) {
|
||||
// For logged-in users, save to profile
|
||||
if (this._id) {
|
||||
return this.setSwimlaneHeight(boardId, swimlaneId, height);
|
||||
}
|
||||
|
||||
// For non-logged-in users, save to localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-swimlane-heights');
|
||||
let heights = stored ? JSON.parse(stored) : {};
|
||||
|
||||
if (!heights[boardId]) {
|
||||
heights[boardId] = {};
|
||||
}
|
||||
heights[boardId][swimlaneId] = height;
|
||||
|
||||
localStorage.setItem('wekan-swimlane-heights', JSON.stringify(heights));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn('Error saving swimlane height to localStorage:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/** returns all confirmed move and copy dialog field values
|
||||
* <li> the board, swimlane and list id is stored for each board
|
||||
*/
|
||||
|
|
@ -1032,6 +1078,144 @@ Users.helpers({
|
|||
_id: this._id,
|
||||
});
|
||||
},
|
||||
|
||||
getListWidthFromStorage(boardId, listId) {
|
||||
// For logged-in users, get from profile
|
||||
if (this._id) {
|
||||
return this.getListWidth(boardId, listId);
|
||||
}
|
||||
|
||||
// For non-logged-in users, get from localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-list-widths');
|
||||
if (stored) {
|
||||
const widths = JSON.parse(stored);
|
||||
if (widths[boardId] && widths[boardId][listId]) {
|
||||
return widths[boardId][listId];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading list widths from localStorage:', e);
|
||||
}
|
||||
|
||||
return 270; // Return default width instead of -1
|
||||
},
|
||||
|
||||
setListWidthToStorage(boardId, listId, width) {
|
||||
// For logged-in users, save to profile
|
||||
if (this._id) {
|
||||
return this.setListWidth(boardId, listId, width);
|
||||
}
|
||||
|
||||
// For non-logged-in users, save to localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-list-widths');
|
||||
let widths = stored ? JSON.parse(stored) : {};
|
||||
|
||||
if (!widths[boardId]) {
|
||||
widths[boardId] = {};
|
||||
}
|
||||
widths[boardId][listId] = width;
|
||||
|
||||
localStorage.setItem('wekan-list-widths', JSON.stringify(widths));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn('Error saving list width to localStorage:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
getListConstraintFromStorage(boardId, listId) {
|
||||
// For logged-in users, get from profile
|
||||
if (this._id) {
|
||||
return this.getListConstraint(boardId, listId);
|
||||
}
|
||||
|
||||
// For non-logged-in users, get from localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-list-constraints');
|
||||
if (stored) {
|
||||
const constraints = JSON.parse(stored);
|
||||
if (constraints[boardId] && constraints[boardId][listId]) {
|
||||
return constraints[boardId][listId];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading list constraints from localStorage:', e);
|
||||
}
|
||||
|
||||
return 550; // Return default constraint instead of -1
|
||||
},
|
||||
|
||||
setListConstraintToStorage(boardId, listId, constraint) {
|
||||
// For logged-in users, save to profile
|
||||
if (this._id) {
|
||||
return this.setListConstraint(boardId, listId, constraint);
|
||||
}
|
||||
|
||||
// For non-logged-in users, save to localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-list-constraints');
|
||||
let constraints = stored ? JSON.parse(stored) : {};
|
||||
|
||||
if (!constraints[boardId]) {
|
||||
constraints[boardId] = {};
|
||||
}
|
||||
constraints[boardId][listId] = constraint;
|
||||
|
||||
localStorage.setItem('wekan-list-constraints', JSON.stringify(constraints));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn('Error saving list constraint to localStorage:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
getSwimlaneHeightFromStorage(boardId, swimlaneId) {
|
||||
// For logged-in users, get from profile
|
||||
if (this._id) {
|
||||
return this.getSwimlaneHeight(boardId, swimlaneId);
|
||||
}
|
||||
|
||||
// For non-logged-in users, get from localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-swimlane-heights');
|
||||
if (stored) {
|
||||
const heights = JSON.parse(stored);
|
||||
if (heights[boardId] && heights[boardId][swimlaneId]) {
|
||||
return heights[boardId][swimlaneId];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading swimlane heights from localStorage:', e);
|
||||
}
|
||||
|
||||
return -1; // Return -1 if not found
|
||||
},
|
||||
|
||||
setSwimlaneHeightToStorage(boardId, swimlaneId, height) {
|
||||
// For logged-in users, save to profile
|
||||
if (this._id) {
|
||||
return this.setSwimlaneHeight(boardId, swimlaneId, height);
|
||||
}
|
||||
|
||||
// For non-logged-in users, save to localStorage
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-swimlane-heights');
|
||||
let heights = stored ? JSON.parse(stored) : {};
|
||||
|
||||
if (!heights[boardId]) {
|
||||
heights[boardId] = {};
|
||||
}
|
||||
heights[boardId][swimlaneId] = height;
|
||||
|
||||
localStorage.setItem('wekan-swimlane-heights', JSON.stringify(heights));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn('Error saving swimlane height to localStorage:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Users.mutations({
|
||||
|
|
@ -1429,6 +1613,24 @@ Meteor.methods({
|
|||
const user = ReactiveCache.getCurrentUser();
|
||||
user.setSwimlaneHeight(boardId, swimlaneId, height);
|
||||
},
|
||||
|
||||
applySwimlaneHeightToStorage(boardId, swimlaneId, height) {
|
||||
check(boardId, String);
|
||||
check(swimlaneId, String);
|
||||
check(height, Number);
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
user.setSwimlaneHeightToStorage(boardId, swimlaneId, height);
|
||||
},
|
||||
|
||||
applyListWidthToStorage(boardId, listId, width, constraint) {
|
||||
check(boardId, String);
|
||||
check(listId, String);
|
||||
check(width, Number);
|
||||
check(constraint, Number);
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
user.setListWidthToStorage(boardId, listId, width);
|
||||
user.setListConstraintToStorage(boardId, listId, constraint);
|
||||
},
|
||||
setZoomLevel(level) {
|
||||
check(level, Number);
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
|
|
|
|||
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -137,7 +137,7 @@
|
|||
"from": "git+https://github.com/wekan/dragscroll.git"
|
||||
},
|
||||
"@wekanteam/exceljs": {
|
||||
"version": "git+https://github.com/wekan/exceljs.git#7d182abf83ddfb1a8f5a9592a0fdf60ef72f6686",
|
||||
"version": "git+https://github.com/wekan/exceljs.git#e0229907e7a81bc3fe6daf4e42b1fdfbecdcb7cb",
|
||||
"from": "git+https://github.com/wekan/exceljs.git",
|
||||
"requires": {
|
||||
"archiver": "^5.0.0",
|
||||
|
|
@ -2517,11 +2517,6 @@
|
|||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
|
||||
},
|
||||
"mongo-object": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mongo-object/-/mongo-object-3.0.1.tgz",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue