From 351433524708e9a7ccb4795d9ca31a78904943ea Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sun, 19 Oct 2025 23:15:55 +0300 Subject: [PATCH] At Public Board, drag resize list width and swimlane height. For logged in users, fix adding labels. Thanks to xet7 ! Fixes #5922 --- CHANGELOG.md | 3 +- client/components/cards/cardCustomFields.js | 7 +- client/components/cards/cardDate.js | 14 ++- client/components/cards/labels.js | 30 ++++- client/components/lists/list.js | 93 ++++++++++++-- client/components/main/header.css | 1 + client/components/sidebar/sidebar.jade | 12 +- client/components/sidebar/sidebar.js | 2 + .../components/swimlanes/swimlaneHeader.jade | 115 +++++++++--------- client/components/swimlanes/swimlanes.jade | 30 ++--- client/components/swimlanes/swimlanes.js | 59 +++++++-- client/lib/popup.js | 4 + models/users.js | 12 +- 13 files changed, 279 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb3b7eba..ba046b6f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,8 @@ This release fixes the following bugs: - [Fix unable to see My Due Cards](https://github.com/wekan/wekan/commit/66b444e2b0c9b2ed5f98cd1ff0cd9222b2d0c624). Thanks to xet7. - Fix drag drop lists. - [Part 1](https://github.com/wekan/wekan/commit/324f3f7794aace800022a24deb5fd5fb36ebd384). + [Part 1](https://github.com/wekan/wekan/commit/324f3f7794aace800022a24deb5fd5fb36ebd384), + [Part 2](https://github.com/wekan/wekan/commit/ff516ec696ef499f11b04b30053eeb9d3f96d8d1). Thanks to xet7. - [Removed extra pipe characters](https://github.com/wekan/wekan/commit/caa6e615ff3c3681bf2b470a625eb39c6009b825). Thanks to xet7. diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index 5d02091ca..333d5e5cd 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -168,7 +168,12 @@ CardCustomField.register('cardCustomField'); } showWeekOfYear() { - return ReactiveCache.getCurrentUser().isShowWeekOfYear(); + const user = ReactiveCache.getCurrentUser(); + if (!user) { + // For non-logged-in users, week of year is not shown + return false; + } + return user.isShowWeekOfYear(); } showDate() { diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 8b87b0cf9..7c8ef242d 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -131,7 +131,12 @@ const CardDate = BlazeComponent.extendComponent({ }, showWeekOfYear() { - return ReactiveCache.getCurrentUser().isShowWeekOfYear(); + const user = ReactiveCache.getCurrentUser(); + if (!user) { + // For non-logged-in users, week of year is not shown + return false; + } + return user.isShowWeekOfYear(); }, showDate() { @@ -301,7 +306,12 @@ class CardCustomFieldDate extends CardDate { } showWeekOfYear() { - return ReactiveCache.getCurrentUser().isShowWeekOfYear(); + const user = ReactiveCache.getCurrentUser(); + if (!user) { + // For non-logged-in users, week of year is not shown + return false; + } + return user.isShowWeekOfYear(); } showDate() { diff --git a/client/components/cards/labels.js b/client/components/cards/labels.js index e09598189..2962cae77 100644 --- a/client/components/cards/labels.js +++ b/client/components/cards/labels.js @@ -125,8 +125,19 @@ Template.createLabelPopup.events({ .$('#labelName') .val() .trim(); - const color = Blaze.getData(templateInstance.find('.fa-check')).color; - board.addLabel(name, color); + + // Find the selected color by looking for the palette color that contains the checkmark + let selectedColor = null; + templateInstance.$('.js-palette-color').each(function() { + if ($(this).text().includes('✅')) { + selectedColor = Blaze.getData(this).color; + return false; // break out of loop + } + }); + + if (selectedColor) { + board.addLabel(name, selectedColor); + } Popup.back(); }, }); @@ -144,8 +155,19 @@ Template.editLabelPopup.events({ .$('#labelName') .val() .trim(); - const color = Blaze.getData(templateInstance.find('.fa-check')).color; - board.editLabel(this._id, name, color); + + // Find the selected color by looking for the palette color that contains the checkmark + let selectedColor = null; + templateInstance.$('.js-palette-color').each(function() { + if ($(this).text().includes('✅')) { + selectedColor = Blaze.getData(this).color; + return false; // break out of loop + } + }); + + if (selectedColor) { + board.editLabel(this._id, name, selectedColor); + } Popup.back(); }, }); diff --git a/client/components/lists/list.js b/client/components/lists/list.js index b80ecff84..7501886ae 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -197,20 +197,60 @@ BlazeComponent.extendComponent({ listWidth() { const user = ReactiveCache.getCurrentUser(); const list = Template.currentData(); - if (!user || !list) return 270; // Return default width if user or list is not available - return user.getListWidthFromStorage(list.boardId, list._id); + 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); + } else { + // 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[list.boardId] && widths[list.boardId][list._id]) { + return widths[list.boardId][list._id]; + } + } + } catch (e) { + console.warn('Error reading list width from localStorage:', e); + } + return 270; // Return default width if not found + } }, listConstraint() { const user = ReactiveCache.getCurrentUser(); const list = Template.currentData(); - if (!user || !list) return 550; // Return default constraint if user or list is not available - return user.getListConstraintFromStorage(list.boardId, list._id); + if (!list) return 550; // Return default constraint if list is not available + + if (user) { + // For logged-in users, get from user profile + return user.getListConstraintFromStorage(list.boardId, list._id); + } else { + // 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[list.boardId] && constraints[list.boardId][list._id]) { + return constraints[list.boardId][list._id]; + } + } + } catch (e) { + console.warn('Error reading list constraint from localStorage:', e); + } + return 550; // Return default constraint if not found + } }, autoWidth() { const user = ReactiveCache.getCurrentUser(); const list = Template.currentData(); + if (!user) { + // For non-logged-in users, auto-width is disabled + return false; + } return user.isAutoWidth(list.boardId); }, @@ -329,14 +369,49 @@ BlazeComponent.extendComponent({ // Use the new storage method that handles both logged-in and non-logged-in users if (process.env.DEBUG === 'true') { } - Meteor.call('applyListWidthToStorage', boardId, listId, finalWidth, listConstraint, (error, result) => { - if (error) { - console.error('Error saving list width:', error); - } else { + + const currentUser = ReactiveCache.getCurrentUser(); + if (currentUser) { + // For logged-in users, use server method + Meteor.call('applyListWidthToStorage', boardId, listId, finalWidth, listConstraint, (error, result) => { + if (error) { + console.error('Error saving list width:', error); + } else { + if (process.env.DEBUG === 'true') { + } + } + }); + } else { + // For non-logged-in users, save to localStorage directly + try { + // 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(); }; diff --git a/client/components/main/header.css b/client/components/main/header.css index 6bb1043f3..f0d002b7a 100644 --- a/client/components/main/header.css +++ b/client/components/main/header.css @@ -220,6 +220,7 @@ margin: 0; margin-top: 1px; } + #header-quick-access #header-user-bar .header-user-bar-name, #header-quick-access #header-help { margin: 4px 8px 0 0; diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 138d4b8bf..d63ea9d98 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -28,7 +28,6 @@ template(name="sidebar") +Template.dynamic(template=getViewTemplate) template(name='homeSidebar') - hr +membersWidget hr +labelsWidget @@ -38,11 +37,12 @@ template(name='homeSidebar') span {{_ 'hide-minicard-label-text'}} b   .materialCheckBox(class="{{#if hiddenMinicardLabelText}}is-checked{{/if}}") - ul#cards.vertical-scrollbars-toggle - a.flex.js-vertical-scrollbars-toggle(title="{{_ 'enable-vertical-scrollbars'}}") - span {{_ 'enable-vertical-scrollbars'}} - b   - .materialCheckBox(class="{{#if isVerticalScrollbars}}is-checked{{/if}}") + if currentUser + ul#cards.vertical-scrollbars-toggle + a.flex.js-vertical-scrollbars-toggle(title="{{_ 'enable-vertical-scrollbars'}}") + span {{_ 'enable-vertical-scrollbars'}} + b   + .materialCheckBox(class="{{#if isVerticalScrollbars}}is-checked{{/if}}") ul#cards.show-week-of-year-toggle a.flex.js-show-week-of-year-toggle(title="{{_ 'show-week-of-year'}}") span {{_ 'show-week-of-year'}} diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index a6de901d3..9f1d9c5c1 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -203,6 +203,8 @@ BlazeComponent.extendComponent({ }, }).register('homeSidebar'); + + Template.boardInfoOnMyBoardsPopup.helpers({ hideCardCounterList() { return Utils.isMiniScreen() && Session.get('currentBoard'); diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index 78fc9d4af..04548ffa6 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -23,26 +23,27 @@ template(name="swimlaneFixedHeader") +viewer | {{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'}}") - | ☰ - //// TODO: Collapse Swimlane: make button working, etc. - //unless collapsed - // a.js-collapse-swimlane(title="{{_ 'collapse'}}") - // i.fa.fa-arrow-down.swimlane-header-collapse-down - // ⬆️.swimlane-header-collapse-up - //if collapsed - // a.js-collapse-swimlane(title="{{_ 'uncollapse'}}") - // ⬆️.swimlane-header-collapse-up - // i.fa.fa-arrow-down.swimlane-header-collapse-down - unless isTouchScreen - a.swimlane-header-handle.handle.js-swimlane-header-handle - | ↕️ - if isTouchScreen - a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle - | ↕️ + if currentUser + unless currentUser.isCommentOnly + a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}") + | ➕ + a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}") + | ☰ + //// TODO: Collapse Swimlane: make button working, etc. + //unless collapsed + // a.js-collapse-swimlane(title="{{_ 'collapse'}}") + // i.fa.fa-arrow-down.swimlane-header-collapse-down + // ⬆️.swimlane-header-collapse-up + //if collapsed + // a.js-collapse-swimlane(title="{{_ 'uncollapse'}}") + // ⬆️.swimlane-header-collapse-up + // i.fa.fa-arrow-down.swimlane-header-collapse-down + unless isTouchScreen + a.swimlane-header-handle.handle.js-swimlane-header-handle + | ↕️ + if isTouchScreen + a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle + | ↕️ template(name="editSwimlaneTitleForm") .list-composer @@ -53,44 +54,46 @@ template(name="editSwimlaneTitleForm") | ❌ template(name="swimlaneActionPopup") - unless currentUser.isCommentOnly - ul.pop-over-list - if currentUser.isBoardAdmin - li: a.js-set-swimlane-color - | 🎨 - | {{_ 'select-color'}} - li: a.js-set-swimlane-height - | ↕️ - | {{_ 'set-swimlane-height'}} - if currentUser.isBoardAdmin - unless this.isTemplateContainer - hr - ul.pop-over-list - li: a.js-close-swimlane - | ▶️ - | 📦 - | {{_ 'archive-swimlane'}} - ul.pop-over-list - li: a.js-copy-swimlane - | 📋 - | {{_ 'copy-swimlane'}} - ul.pop-over-list - li: a.js-move-swimlane - | ⬆️ - | {{_ 'move-swimlane'}} + if currentUser + unless currentUser.isCommentOnly + ul.pop-over-list + if currentUser.isBoardAdmin + li: a.js-set-swimlane-color + | 🎨 + | {{_ 'select-color'}} + li: a.js-set-swimlane-height + | ↕️ + | {{_ 'set-swimlane-height'}} + if currentUser.isBoardAdmin + unless this.isTemplateContainer + hr + ul.pop-over-list + li: a.js-close-swimlane + | ▶️ + | 📦 + | {{_ 'archive-swimlane'}} + ul.pop-over-list + li: a.js-copy-swimlane + | 📋 + | {{_ 'copy-swimlane'}} + ul.pop-over-list + li: a.js-move-swimlane + | ⬆️ + | {{_ 'move-swimlane'}} template(name="swimlaneAddPopup") - unless currentUser.isCommentOnly - form - input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}" - autocomplete="off" autofocus) - .edit-controls.clearfix - button.primary.confirm(type="submit") {{_ 'add'}} - unless currentBoard.isTemplatesBoard - unless currentBoard.isTemplateBoard - span.quiet - | {{_ 'or'}} - a.js-swimlane-template {{_ 'template'}} + if currentUser + unless currentUser.isCommentOnly + form + input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}" + autocomplete="off" autofocus) + .edit-controls.clearfix + button.primary.confirm(type="submit") {{_ 'add'}} + unless currentBoard.isTemplatesBoard + unless currentBoard.isTemplateBoard + span.quiet + | {{_ 'or'}} + a.js-swimlane-template {{_ 'template'}} template(name="setSwimlaneColorPopup") form.edit-label.swimlane-color-popup diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade index 29d8fb62d..25e634573 100644 --- a/client/components/swimlanes/swimlanes.jade +++ b/client/components/swimlanes/swimlanes.jade @@ -72,21 +72,23 @@ template(name="addListForm") | ➕ template(name="moveSwimlanePopup") - unless currentUser.isWorker - label {{_ 'boards'}}: - select.js-select-boards(autofocus) - each toBoard in toBoards - option(value="{{toBoard._id}}") {{toBoard.title}} + if currentUser + unless currentUser.isWorker + label {{_ 'boards'}}: + select.js-select-boards(autofocus) + each toBoard in toBoards + option(value="{{toBoard._id}}") {{toBoard.title}} - .edit-controls.clearfix - button.primary.confirm.js-done {{_ 'done'}} + .edit-controls.clearfix + button.primary.confirm.js-done {{_ 'done'}} template(name="copySwimlanePopup") - unless currentUser.isWorker - label {{_ 'boards'}}: - select.js-select-boards(autofocus) - each toBoard in toBoards - option(value="{{toBoard._id}}" selected="{{#if $eq toBoard.title board.title}}1{{/if}}") {{toBoard.title}} + if currentUser + unless currentUser.isWorker + label {{_ 'boards'}}: + select.js-select-boards(autofocus) + each toBoard in toBoards + option(value="{{toBoard._id}}" selected="{{#if $eq toBoard.title board.title}}1{{/if}}") {{toBoard.title}} - .edit-controls.clearfix - button.primary.confirm.js-done {{_ 'done'}} + .edit-controls.clearfix + button.primary.confirm.js-done {{_ 'done'}} diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js index 398243683..cb0eb4c9d 100644 --- a/client/components/swimlanes/swimlanes.js +++ b/client/components/swimlanes/swimlanes.js @@ -423,7 +423,31 @@ BlazeComponent.extendComponent({ swimlaneHeight() { const user = ReactiveCache.getCurrentUser(); const swimlane = Template.currentData(); - const height = user.getSwimlaneHeightFromStorage(swimlane.boardId, swimlane._id); + + let height; + if (user) { + // For logged-in users, get from user profile + height = user.getSwimlaneHeightFromStorage(swimlane.boardId, swimlane._id); + } else { + // 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[swimlane.boardId] && heights[swimlane.boardId][swimlane._id]) { + height = heights[swimlane.boardId][swimlane._id]; + } else { + height = -1; + } + } else { + height = -1; + } + } catch (e) { + console.warn('Error reading swimlane height from localStorage:', e); + height = -1; + } + } + return height == -1 ? "auto" : (height + 5 + "px"); }, @@ -537,15 +561,36 @@ BlazeComponent.extendComponent({ 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 { + const currentUser = ReactiveCache.getCurrentUser(); + if (currentUser) { + // For logged-in users, use server method + Meteor.call('applySwimlaneHeightToStorage', boardId, swimlaneId, finalHeight, (error, result) => { + if (error) { + console.error('Error saving swimlane height:', error); + } else { + if (process.env.DEBUG === 'true') { + } + } + }); + } else { + // For non-logged-in users, save to localStorage directly + try { + const stored = localStorage.getItem('wekan-swimlane-heights'); + let heights = stored ? JSON.parse(stored) : {}; + + if (!heights[boardId]) { + heights[boardId] = {}; + } + heights[boardId][swimlaneId] = finalHeight; + + localStorage.setItem('wekan-swimlane-heights', JSON.stringify(heights)); + if (process.env.DEBUG === 'true') { } + } catch (e) { + console.warn('Error saving swimlane height to localStorage:', e); } - }); + } e.preventDefault(); }; diff --git a/client/lib/popup.js b/client/lib/popup.js index 5cc5ec27c..5c0c7a6c9 100644 --- a/client/lib/popup.js +++ b/client/lib/popup.js @@ -101,6 +101,10 @@ window.Popup = new (class { // 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, () => { diff --git a/models/users.js b/models/users.js index 081df38d6..0d49d0579 100644 --- a/models/users.js +++ b/models/users.js @@ -1619,7 +1619,10 @@ Meteor.methods({ check(swimlaneId, String); check(height, Number); const user = ReactiveCache.getCurrentUser(); - user.setSwimlaneHeightToStorage(boardId, swimlaneId, height); + if (user) { + user.setSwimlaneHeightToStorage(boardId, swimlaneId, height); + } + // For non-logged-in users, the client-side code will handle localStorage }, applyListWidthToStorage(boardId, listId, width, constraint) { @@ -1628,8 +1631,11 @@ Meteor.methods({ check(width, Number); check(constraint, Number); const user = ReactiveCache.getCurrentUser(); - user.setListWidthToStorage(boardId, listId, width); - user.setListConstraintToStorage(boardId, listId, constraint); + if (user) { + user.setListWidthToStorage(boardId, listId, width); + user.setListConstraintToStorage(boardId, listId, constraint); + } + // For non-logged-in users, the client-side code will handle localStorage }, setZoomLevel(level) { check(level, Number);