At Public Board, drag resize list width and swimlane height. For logged in users, fix adding labels.

Thanks to xet7 !

Fixes #5922
This commit is contained in:
Lauri Ojansivu 2025-10-19 23:15:55 +03:00
parent 55bec31a3f
commit 3514335247
13 changed files with 279 additions and 103 deletions

View file

@ -34,7 +34,8 @@ This release fixes the following bugs:
- [Fix unable to see My Due Cards](https://github.com/wekan/wekan/commit/66b444e2b0c9b2ed5f98cd1ff0cd9222b2d0c624). - [Fix unable to see My Due Cards](https://github.com/wekan/wekan/commit/66b444e2b0c9b2ed5f98cd1ff0cd9222b2d0c624).
Thanks to xet7. Thanks to xet7.
- Fix drag drop lists. - 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. Thanks to xet7.
- [Removed extra pipe characters](https://github.com/wekan/wekan/commit/caa6e615ff3c3681bf2b470a625eb39c6009b825). - [Removed extra pipe characters](https://github.com/wekan/wekan/commit/caa6e615ff3c3681bf2b470a625eb39c6009b825).
Thanks to xet7. Thanks to xet7.

View file

@ -168,7 +168,12 @@ CardCustomField.register('cardCustomField');
} }
showWeekOfYear() { 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() { showDate() {

View file

@ -131,7 +131,12 @@ const CardDate = BlazeComponent.extendComponent({
}, },
showWeekOfYear() { 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() { showDate() {
@ -301,7 +306,12 @@ class CardCustomFieldDate extends CardDate {
} }
showWeekOfYear() { 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() { showDate() {

View file

@ -125,8 +125,19 @@ Template.createLabelPopup.events({
.$('#labelName') .$('#labelName')
.val() .val()
.trim(); .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(); Popup.back();
}, },
}); });
@ -144,8 +155,19 @@ Template.editLabelPopup.events({
.$('#labelName') .$('#labelName')
.val() .val()
.trim(); .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(); Popup.back();
}, },
}); });

View file

@ -197,20 +197,60 @@ BlazeComponent.extendComponent({
listWidth() { listWidth() {
const user = ReactiveCache.getCurrentUser(); const user = ReactiveCache.getCurrentUser();
const list = Template.currentData(); const list = Template.currentData();
if (!user || !list) return 270; // Return default width if user or list is not available if (!list) return 270; // Return default width if list is not available
return user.getListWidthFromStorage(list.boardId, list._id);
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() { listConstraint() {
const user = ReactiveCache.getCurrentUser(); const user = ReactiveCache.getCurrentUser();
const list = Template.currentData(); const list = Template.currentData();
if (!user || !list) return 550; // Return default constraint if user or list is not available if (!list) return 550; // Return default constraint if list is not available
return user.getListConstraintFromStorage(list.boardId, list._id);
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() { autoWidth() {
const user = ReactiveCache.getCurrentUser(); const user = ReactiveCache.getCurrentUser();
const list = Template.currentData(); const list = Template.currentData();
if (!user) {
// For non-logged-in users, auto-width is disabled
return false;
}
return user.isAutoWidth(list.boardId); 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 // Use the new storage method that handles both logged-in and non-logged-in users
if (process.env.DEBUG === 'true') { if (process.env.DEBUG === 'true') {
} }
Meteor.call('applyListWidthToStorage', boardId, listId, finalWidth, listConstraint, (error, result) => {
if (error) { const currentUser = ReactiveCache.getCurrentUser();
console.error('Error saving list width:', error); if (currentUser) {
} else { // 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') { if (process.env.DEBUG === 'true') {
} }
} catch (e) {
console.warn('Error saving list width/constraint to localStorage:', e);
} }
}); }
e.preventDefault(); e.preventDefault();
}; };

View file

@ -220,6 +220,7 @@
margin: 0; margin: 0;
margin-top: 1px; margin-top: 1px;
} }
#header-quick-access #header-user-bar .header-user-bar-name, #header-quick-access #header-user-bar .header-user-bar-name,
#header-quick-access #header-help { #header-quick-access #header-help {
margin: 4px 8px 0 0; margin: 4px 8px 0 0;

View file

@ -28,7 +28,6 @@ template(name="sidebar")
+Template.dynamic(template=getViewTemplate) +Template.dynamic(template=getViewTemplate)
template(name='homeSidebar') template(name='homeSidebar')
hr
+membersWidget +membersWidget
hr hr
+labelsWidget +labelsWidget
@ -38,11 +37,12 @@ template(name='homeSidebar')
span {{_ 'hide-minicard-label-text'}} span {{_ 'hide-minicard-label-text'}}
b   b  
.materialCheckBox(class="{{#if hiddenMinicardLabelText}}is-checked{{/if}}") .materialCheckBox(class="{{#if hiddenMinicardLabelText}}is-checked{{/if}}")
ul#cards.vertical-scrollbars-toggle if currentUser
a.flex.js-vertical-scrollbars-toggle(title="{{_ 'enable-vertical-scrollbars'}}") ul#cards.vertical-scrollbars-toggle
span {{_ 'enable-vertical-scrollbars'}} a.flex.js-vertical-scrollbars-toggle(title="{{_ 'enable-vertical-scrollbars'}}")
b   span {{_ 'enable-vertical-scrollbars'}}
.materialCheckBox(class="{{#if isVerticalScrollbars}}is-checked{{/if}}") b  
.materialCheckBox(class="{{#if isVerticalScrollbars}}is-checked{{/if}}")
ul#cards.show-week-of-year-toggle ul#cards.show-week-of-year-toggle
a.flex.js-show-week-of-year-toggle(title="{{_ 'show-week-of-year'}}") a.flex.js-show-week-of-year-toggle(title="{{_ 'show-week-of-year'}}")
span {{_ 'show-week-of-year'}} span {{_ 'show-week-of-year'}}

View file

@ -203,6 +203,8 @@ BlazeComponent.extendComponent({
}, },
}).register('homeSidebar'); }).register('homeSidebar');
Template.boardInfoOnMyBoardsPopup.helpers({ Template.boardInfoOnMyBoardsPopup.helpers({
hideCardCounterList() { hideCardCounterList() {
return Utils.isMiniScreen() && Session.get('currentBoard'); return Utils.isMiniScreen() && Session.get('currentBoard');

View file

@ -23,26 +23,27 @@ template(name="swimlaneFixedHeader")
+viewer +viewer
| {{isTitleDefault title}} | {{isTitleDefault title}}
.swimlane-header-menu .swimlane-header-menu
unless currentUser.isCommentOnly if currentUser
a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}") 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-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
//// TODO: Collapse Swimlane: make button working, etc. | ☰
//unless collapsed //// TODO: Collapse Swimlane: make button working, etc.
// a.js-collapse-swimlane(title="{{_ 'collapse'}}") //unless collapsed
// i.fa.fa-arrow-down.swimlane-header-collapse-down // a.js-collapse-swimlane(title="{{_ 'collapse'}}")
// ⬆️.swimlane-header-collapse-up // i.fa.fa-arrow-down.swimlane-header-collapse-down
//if collapsed // ⬆️.swimlane-header-collapse-up
// a.js-collapse-swimlane(title="{{_ 'uncollapse'}}") //if collapsed
// ⬆️.swimlane-header-collapse-up // a.js-collapse-swimlane(title="{{_ 'uncollapse'}}")
// i.fa.fa-arrow-down.swimlane-header-collapse-down // ⬆️.swimlane-header-collapse-up
unless isTouchScreen // i.fa.fa-arrow-down.swimlane-header-collapse-down
a.swimlane-header-handle.handle.js-swimlane-header-handle unless isTouchScreen
| ↕️ a.swimlane-header-handle.handle.js-swimlane-header-handle
if isTouchScreen | ↕️
a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle if isTouchScreen
| ↕️ a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
| ↕️
template(name="editSwimlaneTitleForm") template(name="editSwimlaneTitleForm")
.list-composer .list-composer
@ -53,44 +54,46 @@ template(name="editSwimlaneTitleForm")
| ❌ | ❌
template(name="swimlaneActionPopup") template(name="swimlaneActionPopup")
unless currentUser.isCommentOnly if currentUser
ul.pop-over-list unless currentUser.isCommentOnly
if currentUser.isBoardAdmin ul.pop-over-list
li: a.js-set-swimlane-color if currentUser.isBoardAdmin
| 🎨 li: a.js-set-swimlane-color
| {{_ 'select-color'}} | 🎨
li: a.js-set-swimlane-height | {{_ 'select-color'}}
| ↕️ li: a.js-set-swimlane-height
| {{_ 'set-swimlane-height'}} | ↕️
if currentUser.isBoardAdmin | {{_ 'set-swimlane-height'}}
unless this.isTemplateContainer if currentUser.isBoardAdmin
hr unless this.isTemplateContainer
ul.pop-over-list hr
li: a.js-close-swimlane ul.pop-over-list
| ▶️ li: a.js-close-swimlane
| 📦 | ▶️
| {{_ 'archive-swimlane'}} | 📦
ul.pop-over-list | {{_ 'archive-swimlane'}}
li: a.js-copy-swimlane ul.pop-over-list
| 📋 li: a.js-copy-swimlane
| {{_ 'copy-swimlane'}} | 📋
ul.pop-over-list | {{_ 'copy-swimlane'}}
li: a.js-move-swimlane ul.pop-over-list
| ⬆️ li: a.js-move-swimlane
| {{_ 'move-swimlane'}} | ⬆️
| {{_ 'move-swimlane'}}
template(name="swimlaneAddPopup") template(name="swimlaneAddPopup")
unless currentUser.isCommentOnly if currentUser
form unless currentUser.isCommentOnly
input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}" form
autocomplete="off" autofocus) input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}"
.edit-controls.clearfix autocomplete="off" autofocus)
button.primary.confirm(type="submit") {{_ 'add'}} .edit-controls.clearfix
unless currentBoard.isTemplatesBoard button.primary.confirm(type="submit") {{_ 'add'}}
unless currentBoard.isTemplateBoard unless currentBoard.isTemplatesBoard
span.quiet unless currentBoard.isTemplateBoard
| {{_ 'or'}} span.quiet
a.js-swimlane-template {{_ 'template'}} | {{_ 'or'}}
a.js-swimlane-template {{_ 'template'}}
template(name="setSwimlaneColorPopup") template(name="setSwimlaneColorPopup")
form.edit-label.swimlane-color-popup form.edit-label.swimlane-color-popup

View file

@ -72,21 +72,23 @@ template(name="addListForm")
| |
template(name="moveSwimlanePopup") template(name="moveSwimlanePopup")
unless currentUser.isWorker if currentUser
label {{_ 'boards'}}: unless currentUser.isWorker
select.js-select-boards(autofocus) label {{_ 'boards'}}:
each toBoard in toBoards select.js-select-boards(autofocus)
option(value="{{toBoard._id}}") {{toBoard.title}} each toBoard in toBoards
option(value="{{toBoard._id}}") {{toBoard.title}}
.edit-controls.clearfix .edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}} button.primary.confirm.js-done {{_ 'done'}}
template(name="copySwimlanePopup") template(name="copySwimlanePopup")
unless currentUser.isWorker if currentUser
label {{_ 'boards'}}: unless currentUser.isWorker
select.js-select-boards(autofocus) label {{_ 'boards'}}:
each toBoard in toBoards select.js-select-boards(autofocus)
option(value="{{toBoard._id}}" selected="{{#if $eq toBoard.title board.title}}1{{/if}}") {{toBoard.title}} each toBoard in toBoards
option(value="{{toBoard._id}}" selected="{{#if $eq toBoard.title board.title}}1{{/if}}") {{toBoard.title}}
.edit-controls.clearfix .edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}} button.primary.confirm.js-done {{_ 'done'}}

View file

@ -423,7 +423,31 @@ BlazeComponent.extendComponent({
swimlaneHeight() { swimlaneHeight() {
const user = ReactiveCache.getCurrentUser(); const user = ReactiveCache.getCurrentUser();
const swimlane = Template.currentData(); 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"); return height == -1 ? "auto" : (height + 5 + "px");
}, },
@ -537,15 +561,36 @@ BlazeComponent.extendComponent({
if (process.env.DEBUG === 'true') { if (process.env.DEBUG === 'true') {
} }
// Use the new storage method that handles both logged-in and non-logged-in users const currentUser = ReactiveCache.getCurrentUser();
Meteor.call('applySwimlaneHeightToStorage', boardId, swimlaneId, finalHeight, (error, result) => { if (currentUser) {
if (error) { // For logged-in users, use server method
console.error('Error saving swimlane height:', error); Meteor.call('applySwimlaneHeightToStorage', boardId, swimlaneId, finalHeight, (error, result) => {
} else { 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') { if (process.env.DEBUG === 'true') {
} }
} catch (e) {
console.warn('Error saving swimlane height to localStorage:', e);
} }
}); }
e.preventDefault(); e.preventDefault();
}; };

View file

@ -101,6 +101,10 @@ window.Popup = new (class {
// our internal dependency, and since we just changed the top element of // our internal dependency, and since we just changed the top element of
// our internal stack, the popup will be updated with the new data. // our internal stack, the popup will be updated with the new data.
if (!self.isOpen()) { if (!self.isOpen()) {
if (!Template[popupName]) {
console.error('Template not found:', popupName);
return;
}
self.current = Blaze.renderWithData( self.current = Blaze.renderWithData(
self.template, self.template,
() => { () => {

View file

@ -1619,7 +1619,10 @@ Meteor.methods({
check(swimlaneId, String); check(swimlaneId, String);
check(height, Number); check(height, Number);
const user = ReactiveCache.getCurrentUser(); 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) { applyListWidthToStorage(boardId, listId, width, constraint) {
@ -1628,8 +1631,11 @@ Meteor.methods({
check(width, Number); check(width, Number);
check(constraint, Number); check(constraint, Number);
const user = ReactiveCache.getCurrentUser(); const user = ReactiveCache.getCurrentUser();
user.setListWidthToStorage(boardId, listId, width); if (user) {
user.setListConstraintToStorage(boardId, listId, constraint); user.setListWidthToStorage(boardId, listId, width);
user.setListConstraintToStorage(boardId, listId, constraint);
}
// For non-logged-in users, the client-side code will handle localStorage
}, },
setZoomLevel(level) { setZoomLevel(level) {
check(level, Number); check(level, Number);