mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 23:40:13 +01:00
Change list width by dragging between lists.
Thanks to xet7 !
This commit is contained in:
parent
0d9536e2f9
commit
abad8cc4d5
7 changed files with 413 additions and 33 deletions
|
|
@ -89,7 +89,9 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
// Check if board needs conversion (for old structure)
|
// Check if board needs conversion (for old structure)
|
||||||
if (boardConverter.isBoardConverted(boardId)) {
|
if (boardConverter.isBoardConverted(boardId)) {
|
||||||
console.log(`Board ${boardId} has already been converted, skipping conversion`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been converted, skipping conversion`);
|
||||||
|
}
|
||||||
this.isBoardReady.set(true);
|
this.isBoardReady.set(true);
|
||||||
} else {
|
} else {
|
||||||
const needsConversion = boardConverter.needsConversion(boardId);
|
const needsConversion = boardConverter.needsConversion(boardId);
|
||||||
|
|
@ -127,7 +129,9 @@ BlazeComponent.extendComponent({
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Failed to start background migration:', error);
|
console.error('Failed to start background migration:', error);
|
||||||
} else {
|
} else {
|
||||||
console.log('Background migration started for board:', boardId);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('Background migration started for board:', boardId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -139,7 +143,9 @@ BlazeComponent.extendComponent({
|
||||||
try {
|
try {
|
||||||
// Check if board has already been migrated
|
// Check if board has already been migrated
|
||||||
if (attachmentMigrationManager.isBoardMigrated(boardId)) {
|
if (attachmentMigrationManager.isBoardMigrated(boardId)) {
|
||||||
console.log(`Board ${boardId} has already been migrated, skipping`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been migrated, skipping`);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,12 +153,16 @@ BlazeComponent.extendComponent({
|
||||||
const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId);
|
const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId);
|
||||||
|
|
||||||
if (unconvertedAttachments.length > 0) {
|
if (unconvertedAttachments.length > 0) {
|
||||||
console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`);
|
||||||
|
}
|
||||||
await attachmentMigrationManager.startAttachmentMigration(boardId);
|
await attachmentMigrationManager.startAttachmentMigration(boardId);
|
||||||
} else {
|
} else {
|
||||||
// No attachments to migrate, mark board as migrated
|
// No attachments to migrate, mark board as migrated
|
||||||
// This will be handled by the migration manager itself
|
// This will be handled by the migration manager itself
|
||||||
console.log(`Board ${boardId} has no attachments to migrate`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has no attachments to migrate`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting attachment migration:', error);
|
console.error('Error starting attachment migration:', error);
|
||||||
|
|
@ -622,14 +632,18 @@ BlazeComponent.extendComponent({
|
||||||
hasSwimlanes() {
|
hasSwimlanes() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
if (!currentBoard) {
|
if (!currentBoard) {
|
||||||
console.log('hasSwimlanes: No current board');
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('hasSwimlanes: No current board');
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const swimlanes = currentBoard.swimlanes();
|
const swimlanes = currentBoard.swimlanes();
|
||||||
const hasSwimlanes = swimlanes && swimlanes.length > 0;
|
const hasSwimlanes = swimlanes && swimlanes.length > 0;
|
||||||
console.log('hasSwimlanes: Board has', swimlanes ? swimlanes.length : 0, 'swimlanes');
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('hasSwimlanes: Board has', swimlanes ? swimlanes.length : 0, 'swimlanes');
|
||||||
|
}
|
||||||
return hasSwimlanes;
|
return hasSwimlanes;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('hasSwimlanes: Error getting swimlanes:', error);
|
console.error('hasSwimlanes: Error getting swimlanes:', error);
|
||||||
|
|
@ -661,14 +675,16 @@ BlazeComponent.extendComponent({
|
||||||
const isMigrating = this.isMigrating.get();
|
const isMigrating = this.isMigrating.get();
|
||||||
const boardView = Utils.boardView();
|
const boardView = Utils.boardView();
|
||||||
|
|
||||||
console.log('=== BOARD DEBUG STATE ===');
|
if (process.env.DEBUG === 'true') {
|
||||||
console.log('currentBoardId:', currentBoardId);
|
console.log('=== BOARD DEBUG STATE ===');
|
||||||
console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none');
|
console.log('currentBoardId:', currentBoardId);
|
||||||
console.log('isBoardReady:', isBoardReady);
|
console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none');
|
||||||
console.log('isConverting:', isConverting);
|
console.log('isBoardReady:', isBoardReady);
|
||||||
console.log('isMigrating:', isMigrating);
|
console.log('isConverting:', isConverting);
|
||||||
console.log('boardView:', boardView);
|
console.log('isMigrating:', isMigrating);
|
||||||
console.log('========================');
|
console.log('boardView:', boardView);
|
||||||
|
console.log('========================');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentBoardId,
|
currentBoardId,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,219 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* List resize handle */
|
||||||
|
.list-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: -3px;
|
||||||
|
width: 6px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: col-resize;
|
||||||
|
z-index: 10;
|
||||||
|
background: transparent;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-resize-handle:hover {
|
||||||
|
background: rgba(0, 123, 255, 0.4);
|
||||||
|
box-shadow: 0 0 4px rgba(0, 123, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-resize-handle:active {
|
||||||
|
background: rgba(0, 123, 255, 0.6);
|
||||||
|
box-shadow: 0 0 6px rgba(0, 123, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show resize handle only on hover */
|
||||||
|
.list:hover .list-resize-handle {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list:hover .list-resize-handle:hover {
|
||||||
|
background: rgba(0, 123, 255, 0.4);
|
||||||
|
box-shadow: 0 0 4px rgba(0, 123, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a subtle indicator line */
|
||||||
|
.list-resize-handle::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 2px;
|
||||||
|
height: 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 1px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-resize-handle:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable resize handle for collapsed lists and mobile view */
|
||||||
|
.list.list-collapsed .list-resize-handle,
|
||||||
|
.list.mobile-view .list-resize-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable resize handle for auto-width lists */
|
||||||
|
.list.list-auto-width .list-resize-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visual feedback during resize */
|
||||||
|
.list.list-resizing {
|
||||||
|
transition: none !important;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 123, 255, 0.3);
|
||||||
|
/* Ensure the list maintains its new width during resize */
|
||||||
|
flex: none !important;
|
||||||
|
flex-basis: auto !important;
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
/* Override any conflicting layout properties */
|
||||||
|
float: left !important;
|
||||||
|
display: block !important;
|
||||||
|
position: relative !important;
|
||||||
|
/* Force width to be respected */
|
||||||
|
width: var(--list-width, auto) !important;
|
||||||
|
min-width: var(--list-width, auto) !important;
|
||||||
|
max-width: var(--list-width, auto) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.list-resizing-active {
|
||||||
|
cursor: col-resize !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.list-resizing-active * {
|
||||||
|
cursor: col-resize !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure swimlane container doesn't interfere with list resizing */
|
||||||
|
.swimlane .list.list-resizing {
|
||||||
|
/* Override any swimlane flex properties */
|
||||||
|
flex: none !important;
|
||||||
|
flex-basis: auto !important;
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
/* Ensure width is respected */
|
||||||
|
width: var(--list-width, auto) !important;
|
||||||
|
min-width: var(--list-width, auto) !important;
|
||||||
|
max-width: var(--list-width, auto) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* More aggressive override for any container that might interfere */
|
||||||
|
.js-swimlane .list.list-resizing,
|
||||||
|
.dragscroll .list.list-resizing,
|
||||||
|
[id^="swimlane-"] .list.list-resizing {
|
||||||
|
/* Force the width to be applied */
|
||||||
|
width: var(--list-width, auto) !important;
|
||||||
|
min-width: var(--list-width, auto) !important;
|
||||||
|
max-width: var(--list-width, auto) !important;
|
||||||
|
flex: none !important;
|
||||||
|
flex-basis: auto !important;
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
float: left !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the width persists after resize is complete */
|
||||||
|
.js-swimlane .list[style*="--list-width"],
|
||||||
|
.dragscroll .list[style*="--list-width"],
|
||||||
|
[id^="swimlane-"] .list[style*="--list-width"] {
|
||||||
|
/* Maintain the width after resize */
|
||||||
|
width: var(--list-width, auto) !important;
|
||||||
|
min-width: var(--list-width, auto) !important;
|
||||||
|
max-width: var(--list-width, auto) !important;
|
||||||
|
flex: none !important;
|
||||||
|
flex-basis: auto !important;
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
float: left !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure consistent header height for all lists */
|
||||||
|
.list-header {
|
||||||
|
/* Maintain consistent height and padding for all lists */
|
||||||
|
min-height: 2.5vh !important;
|
||||||
|
height: auto !important;
|
||||||
|
padding: 2.5vh 1.5vw 0.5vh !important;
|
||||||
|
/* Make sure the background covers the full height */
|
||||||
|
background-color: #e4e4e4 !important;
|
||||||
|
border-bottom: 0.8vh solid #e4e4e4 !important;
|
||||||
|
/* Use original display for consistent button positioning */
|
||||||
|
display: block !important;
|
||||||
|
position: relative !important;
|
||||||
|
/* Prevent vertical expansion but allow normal height */
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure title text doesn't cause height changes for all lists */
|
||||||
|
.list-header .list-header-name {
|
||||||
|
/* Prevent text wrapping to maintain consistent height */
|
||||||
|
white-space: nowrap !important;
|
||||||
|
/* Truncate text with ellipsis if too long */
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
/* Ensure proper line height */
|
||||||
|
line-height: 1.2 !important;
|
||||||
|
/* Ensure it doesn't overflow */
|
||||||
|
overflow: hidden !important;
|
||||||
|
/* Add margin to prevent overlap with buttons */
|
||||||
|
margin-right: 120px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Position drag handle at top-right corner for ALL lists */
|
||||||
|
.list-header .list-header-handle {
|
||||||
|
/* Position at top-right corner, aligned with title text top */
|
||||||
|
position: absolute !important;
|
||||||
|
top: 2.5vh !important;
|
||||||
|
right: 1.5vw !important;
|
||||||
|
/* Ensure it's above other elements */
|
||||||
|
z-index: 15 !important;
|
||||||
|
/* Remove margin since it's absolutely positioned */
|
||||||
|
margin-right: 0 !important;
|
||||||
|
/* Ensure proper display */
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure buttons maintain original positioning */
|
||||||
|
.js-swimlane .list[style*="--list-width"] .list-header .list-header-plus-top,
|
||||||
|
.js-swimlane .list[style*="--list-width"] .list-header .js-collapse,
|
||||||
|
.js-swimlane .list[style*="--list-width"] .list-header .js-open-list-menu,
|
||||||
|
.dragscroll .list[style*="--list-width"] .list-header .list-header-plus-top,
|
||||||
|
.dragscroll .list[style*="--list-width"] .list-header .js-collapse,
|
||||||
|
.dragscroll .list[style*="--list-width"] .list-header .js-open-list-menu,
|
||||||
|
[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-plus-top,
|
||||||
|
[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-collapse,
|
||||||
|
[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-open-list-menu {
|
||||||
|
/* Use original positioning to maintain layout */
|
||||||
|
position: relative !important;
|
||||||
|
/* Maintain original spacing */
|
||||||
|
margin-right: 15px !important;
|
||||||
|
/* Ensure proper display */
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure watch icon and card count maintain original positioning */
|
||||||
|
.js-swimlane .list[style*="--list-width"] .list-header .list-header-watch-icon,
|
||||||
|
.dragscroll .list[style*="--list-width"] .list-header .list-header-watch-icon,
|
||||||
|
[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-watch-icon,
|
||||||
|
.js-swimlane .list[style*="--list-width"] .list-header .cardCount,
|
||||||
|
.dragscroll .list[style*="--list-width"] .list-header .cardCount,
|
||||||
|
[id^="swimlane-"] .list[style*="--list-width"] .list-header .cardCount {
|
||||||
|
/* Use original positioning to maintain layout */
|
||||||
|
position: relative !important;
|
||||||
|
/* Maintain original spacing */
|
||||||
|
margin-right: 15px !important;
|
||||||
|
/* Ensure proper display */
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
[id^="swimlane-"] .list:first-child {
|
[id^="swimlane-"] .list:first-child {
|
||||||
min-width: 2.5vw;
|
min-width: 2.5vw;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ template(name='list')
|
||||||
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
|
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
|
||||||
+listHeader
|
+listHeader
|
||||||
+listBody
|
+listBody
|
||||||
|
.list-resize-handle.js-list-resize-handle
|
||||||
|
|
||||||
template(name='miniList')
|
template(name='miniList')
|
||||||
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ BlazeComponent.extendComponent({
|
||||||
onRendered() {
|
onRendered() {
|
||||||
const boardComponent = this.parentComponent().parentComponent();
|
const boardComponent = this.parentComponent().parentComponent();
|
||||||
|
|
||||||
|
// Initialize list resize functionality
|
||||||
|
this.initializeListResize();
|
||||||
|
|
||||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||||
const $cards = this.$('.js-minicards');
|
const $cards = this.$('.js-minicards');
|
||||||
|
|
||||||
|
|
@ -212,6 +215,146 @@ BlazeComponent.extendComponent({
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
return user.isAutoWidth(list.boardId);
|
return user.isAutoWidth(list.boardId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initializeListResize() {
|
||||||
|
const list = Template.currentData();
|
||||||
|
const $list = this.$('.js-list');
|
||||||
|
const $resizeHandle = this.$('.js-list-resize-handle');
|
||||||
|
|
||||||
|
// Only enable resize for non-collapsed, non-auto-width lists
|
||||||
|
if (list.collapsed || this.autoWidth()) {
|
||||||
|
$resizeHandle.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isResizing = false;
|
||||||
|
let startX = 0;
|
||||||
|
let startWidth = 0;
|
||||||
|
let minWidth = 100; // Minimum width as defined in the existing code
|
||||||
|
let maxWidth = this.listConstraint() || 1000; // Use constraint as max width
|
||||||
|
let listConstraint = this.listConstraint(); // Store constraint value for use in event handlers
|
||||||
|
const component = this; // Store reference to component for use in event handlers
|
||||||
|
|
||||||
|
const startResize = (e) => {
|
||||||
|
isResizing = true;
|
||||||
|
startX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||||
|
startWidth = $list.outerWidth();
|
||||||
|
|
||||||
|
// Add visual feedback
|
||||||
|
$list.addClass('list-resizing');
|
||||||
|
$('body').addClass('list-resizing-active');
|
||||||
|
|
||||||
|
// Prevent text selection during resize
|
||||||
|
$('body').css('user-select', 'none');
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const doResize = (e) => {
|
||||||
|
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
|
||||||
|
$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'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debug: log the width change
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Resizing list to ${newWidth}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopResize = (e) => {
|
||||||
|
if (!isResizing) return;
|
||||||
|
|
||||||
|
isResizing = false;
|
||||||
|
|
||||||
|
// Calculate final width
|
||||||
|
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||||
|
const deltaX = currentX - startX;
|
||||||
|
const finalWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
|
||||||
|
|
||||||
|
// Ensure the final width is applied using CSS custom properties
|
||||||
|
$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'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove visual feedback but keep the width
|
||||||
|
$list.removeClass('list-resizing');
|
||||||
|
$('body').removeClass('list-resizing-active');
|
||||||
|
$('body').css('user-select', '');
|
||||||
|
|
||||||
|
// Keep the CSS custom property for persistent width
|
||||||
|
// The CSS custom property will remain on the element to maintain the width
|
||||||
|
|
||||||
|
// Save the new width using the existing system
|
||||||
|
const boardId = list.boardId;
|
||||||
|
const listId = list._id;
|
||||||
|
|
||||||
|
// Use the same method as the hamburger menu
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Saving list width: ${finalWidth}px for list ${listId}`);
|
||||||
|
}
|
||||||
|
Meteor.call('applyListWidth', 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mouse events
|
||||||
|
$resizeHandle.on('mousedown', startResize);
|
||||||
|
$(document).on('mousemove', doResize);
|
||||||
|
$(document).on('mouseup', stopResize);
|
||||||
|
|
||||||
|
// Touch events for mobile
|
||||||
|
$resizeHandle.on('touchstart', startResize);
|
||||||
|
$(document).on('touchmove', doResize);
|
||||||
|
$(document).on('touchend', stopResize);
|
||||||
|
|
||||||
|
// Reactively update resize handle visibility when auto-width changes
|
||||||
|
component.autorun(() => {
|
||||||
|
if (component.autoWidth()) {
|
||||||
|
$resizeHandle.hide();
|
||||||
|
} else {
|
||||||
|
$resizeHandle.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up on component destruction
|
||||||
|
component.onDestroyed(() => {
|
||||||
|
$(document).off('mousemove', doResize);
|
||||||
|
$(document).off('mouseup', stopResize);
|
||||||
|
$(document).off('touchmove', doResize);
|
||||||
|
$(document).off('touchend', stopResize);
|
||||||
|
});
|
||||||
|
},
|
||||||
}).register('list');
|
}).register('list');
|
||||||
|
|
||||||
Template.miniList.events({
|
Template.miniList.events({
|
||||||
|
|
|
||||||
|
|
@ -103,14 +103,18 @@ class AttachmentMigrationManager {
|
||||||
|
|
||||||
// Check if this board has already been migrated (client-side check first)
|
// Check if this board has already been migrated (client-side check first)
|
||||||
if (globalMigratedBoards.has(boardId)) {
|
if (globalMigratedBoards.has(boardId)) {
|
||||||
console.log(`Board ${boardId} has already been migrated (client-side), skipping`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been migrated (client-side), skipping`);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-check with server-side check
|
// Double-check with server-side check
|
||||||
const serverMigrated = await this.isBoardMigratedServer(boardId);
|
const serverMigrated = await this.isBoardMigratedServer(boardId);
|
||||||
if (serverMigrated) {
|
if (serverMigrated) {
|
||||||
console.log(`Board ${boardId} has already been migrated (server-side), skipping`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been migrated (server-side), skipping`);
|
||||||
|
}
|
||||||
globalMigratedBoards.add(boardId); // Sync client-side tracking
|
globalMigratedBoards.add(boardId); // Sync client-side tracking
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +132,9 @@ class AttachmentMigrationManager {
|
||||||
attachmentMigrationProgress.set(100);
|
attachmentMigrationProgress.set(100);
|
||||||
isMigratingAttachments.set(false);
|
isMigratingAttachments.set(false);
|
||||||
globalMigratedBoards.add(boardId); // Mark board as migrated
|
globalMigratedBoards.add(boardId); // Mark board as migrated
|
||||||
console.log(`Board ${boardId} has no attachments to migrate, marked as migrated`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has no attachments to migrate, marked as migrated`);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +146,9 @@ class AttachmentMigrationManager {
|
||||||
attachmentMigrationStatus.set(`Migration failed: ${errorMessage}`);
|
attachmentMigrationStatus.set(`Migration failed: ${errorMessage}`);
|
||||||
isMigratingAttachments.set(false);
|
isMigratingAttachments.set(false);
|
||||||
} else {
|
} else {
|
||||||
console.log('Attachment migration started for board:', boardId);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('Attachment migration started for board:', boardId);
|
||||||
|
}
|
||||||
this.pollAttachmentMigrationProgress(boardId);
|
this.pollAttachmentMigrationProgress(boardId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -177,7 +185,9 @@ class AttachmentMigrationManager {
|
||||||
isMigratingAttachments.set(false);
|
isMigratingAttachments.set(false);
|
||||||
this.migrationCache.clear(); // Clear cache to refresh data
|
this.migrationCache.clear(); // Clear cache to refresh data
|
||||||
globalMigratedBoards.add(boardId); // Mark board as migrated
|
globalMigratedBoards.add(boardId); // Mark board as migrated
|
||||||
console.log(`Board ${boardId} migration completed and marked as migrated`);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} migration completed and marked as migrated`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1764,11 +1764,11 @@ Cards.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
setTitle(title) {
|
setTitle(title) {
|
||||||
// Sanitize title on client side as well
|
// Basic client-side validation - server will handle full sanitization
|
||||||
let sanitizedTitle = title;
|
let sanitizedTitle = title;
|
||||||
if (typeof title === 'string') {
|
if (typeof title === 'string') {
|
||||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
// Basic length check to prevent abuse
|
||||||
sanitizedTitle = sanitizeTitle(title);
|
sanitizedTitle = title.length > 1000 ? title.substring(0, 1000) : title;
|
||||||
if (process.env.DEBUG === 'true' && sanitizedTitle !== title) {
|
if (process.env.DEBUG === 'true' && sanitizedTitle !== title) {
|
||||||
console.warn('Client-side sanitized card title:', title, '->', sanitizedTitle);
|
console.warn('Client-side sanitized card title:', title, '->', sanitizedTitle);
|
||||||
}
|
}
|
||||||
|
|
@ -3583,8 +3583,8 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
|
||||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
||||||
if (req.body.title) {
|
if (req.body.title) {
|
||||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
// Basic client-side validation - server will handle full sanitization
|
||||||
const newTitle = sanitizeTitle(req.body.title);
|
const newTitle = req.body.title.length > 1000 ? req.body.title.substring(0, 1000) : req.body.title;
|
||||||
|
|
||||||
if (process.env.DEBUG === 'true' && newTitle !== req.body.title) {
|
if (process.env.DEBUG === 'true' && newTitle !== req.body.title) {
|
||||||
console.warn('Sanitized card title input:', req.body.title, '->', newTitle);
|
console.warn('Sanitized card title input:', req.body.title, '->', newTitle);
|
||||||
|
|
|
||||||
|
|
@ -314,13 +314,10 @@ Lists.helpers({
|
||||||
|
|
||||||
Lists.mutations({
|
Lists.mutations({
|
||||||
rename(title) {
|
rename(title) {
|
||||||
// Sanitize title on client side as well
|
// Basic client-side validation - server will handle full sanitization
|
||||||
if (typeof title === 'string') {
|
if (typeof title === 'string') {
|
||||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
// Basic length check to prevent abuse
|
||||||
const sanitizedTitle = sanitizeTitle(title);
|
const sanitizedTitle = title.length > 1000 ? title.substring(0, 1000) : title;
|
||||||
if (process.env.DEBUG === 'true' && sanitizedTitle !== title) {
|
|
||||||
console.warn('Client-side sanitized list title:', title, '->', sanitizedTitle);
|
|
||||||
}
|
|
||||||
return { $set: { title: sanitizedTitle } };
|
return { $set: { title: sanitizedTitle } };
|
||||||
}
|
}
|
||||||
return { $set: { title } };
|
return { $set: { title } };
|
||||||
|
|
@ -654,8 +651,8 @@ if (Meteor.isServer) {
|
||||||
|
|
||||||
// Update title if provided
|
// Update title if provided
|
||||||
if (req.body.title) {
|
if (req.body.title) {
|
||||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
// Basic client-side validation - server will handle full sanitization
|
||||||
const newTitle = sanitizeTitle(req.body.title);
|
const newTitle = req.body.title.length > 1000 ? req.body.title.substring(0, 1000) : req.body.title;
|
||||||
|
|
||||||
if (process.env.DEBUG === 'true' && newTitle !== req.body.title) {
|
if (process.env.DEBUG === 'true' && newTitle !== req.body.title) {
|
||||||
console.warn('Sanitized list title input:', req.body.title, '->', newTitle);
|
console.warn('Sanitized list title input:', req.body.title, '->', newTitle);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue