mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30: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)
|
||||
if (boardConverter.isBoardConverted(boardId)) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has already been converted, skipping conversion`);
|
||||
}
|
||||
this.isBoardReady.set(true);
|
||||
} else {
|
||||
const needsConversion = boardConverter.needsConversion(boardId);
|
||||
|
|
@ -127,8 +129,10 @@ BlazeComponent.extendComponent({
|
|||
if (error) {
|
||||
console.error('Failed to start background migration:', error);
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Background migration started for board:', boardId);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error starting background migration:', error);
|
||||
|
|
@ -139,7 +143,9 @@ BlazeComponent.extendComponent({
|
|||
try {
|
||||
// Check if board has already been migrated
|
||||
if (attachmentMigrationManager.isBoardMigrated(boardId)) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has already been migrated, skipping`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -147,13 +153,17 @@ BlazeComponent.extendComponent({
|
|||
const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId);
|
||||
|
||||
if (unconvertedAttachments.length > 0) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`);
|
||||
}
|
||||
await attachmentMigrationManager.startAttachmentMigration(boardId);
|
||||
} else {
|
||||
// No attachments to migrate, mark board as migrated
|
||||
// This will be handled by the migration manager itself
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has no attachments to migrate`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting attachment migration:', error);
|
||||
}
|
||||
|
|
@ -622,14 +632,18 @@ BlazeComponent.extendComponent({
|
|||
hasSwimlanes() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (!currentBoard) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('hasSwimlanes: No current board');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const swimlanes = currentBoard.swimlanes();
|
||||
const hasSwimlanes = swimlanes && swimlanes.length > 0;
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('hasSwimlanes: Board has', swimlanes ? swimlanes.length : 0, 'swimlanes');
|
||||
}
|
||||
return hasSwimlanes;
|
||||
} catch (error) {
|
||||
console.error('hasSwimlanes: Error getting swimlanes:', error);
|
||||
|
|
@ -661,6 +675,7 @@ BlazeComponent.extendComponent({
|
|||
const isMigrating = this.isMigrating.get();
|
||||
const boardView = Utils.boardView();
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('=== BOARD DEBUG STATE ===');
|
||||
console.log('currentBoardId:', currentBoardId);
|
||||
console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none');
|
||||
|
|
@ -669,6 +684,7 @@ BlazeComponent.extendComponent({
|
|||
console.log('isMigrating:', isMigrating);
|
||||
console.log('boardView:', boardView);
|
||||
console.log('========================');
|
||||
}
|
||||
|
||||
return {
|
||||
currentBoardId,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,219 @@
|
|||
padding: 0;
|
||||
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 {
|
||||
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}}")
|
||||
+listHeader
|
||||
+listBody
|
||||
.list-resize-handle.js-list-resize-handle
|
||||
|
||||
template(name='miniList')
|
||||
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() {
|
||||
const boardComponent = this.parentComponent().parentComponent();
|
||||
|
||||
// Initialize list resize functionality
|
||||
this.initializeListResize();
|
||||
|
||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
const $cards = this.$('.js-minicards');
|
||||
|
||||
|
|
@ -212,6 +215,146 @@ BlazeComponent.extendComponent({
|
|||
const list = Template.currentData();
|
||||
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');
|
||||
|
||||
Template.miniList.events({
|
||||
|
|
|
|||
|
|
@ -103,14 +103,18 @@ class AttachmentMigrationManager {
|
|||
|
||||
// Check if this board has already been migrated (client-side check first)
|
||||
if (globalMigratedBoards.has(boardId)) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has already been migrated (client-side), skipping`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Double-check with server-side check
|
||||
const serverMigrated = await this.isBoardMigratedServer(boardId);
|
||||
if (serverMigrated) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has already been migrated (server-side), skipping`);
|
||||
}
|
||||
globalMigratedBoards.add(boardId); // Sync client-side tracking
|
||||
return;
|
||||
}
|
||||
|
|
@ -128,7 +132,9 @@ class AttachmentMigrationManager {
|
|||
attachmentMigrationProgress.set(100);
|
||||
isMigratingAttachments.set(false);
|
||||
globalMigratedBoards.add(boardId); // Mark board as migrated
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has no attachments to migrate, marked as migrated`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +146,9 @@ class AttachmentMigrationManager {
|
|||
attachmentMigrationStatus.set(`Migration failed: ${errorMessage}`);
|
||||
isMigratingAttachments.set(false);
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Attachment migration started for board:', boardId);
|
||||
}
|
||||
this.pollAttachmentMigrationProgress(boardId);
|
||||
}
|
||||
});
|
||||
|
|
@ -177,9 +185,11 @@ class AttachmentMigrationManager {
|
|||
isMigratingAttachments.set(false);
|
||||
this.migrationCache.clear(); // Clear cache to refresh data
|
||||
globalMigratedBoards.add(boardId); // Mark board as migrated
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} migration completed and marked as migrated`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1764,11 +1764,11 @@ Cards.helpers({
|
|||
},
|
||||
|
||||
setTitle(title) {
|
||||
// Sanitize title on client side as well
|
||||
// Basic client-side validation - server will handle full sanitization
|
||||
let sanitizedTitle = title;
|
||||
if (typeof title === 'string') {
|
||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
||||
sanitizedTitle = sanitizeTitle(title);
|
||||
// Basic length check to prevent abuse
|
||||
sanitizedTitle = title.length > 1000 ? title.substring(0, 1000) : title;
|
||||
if (process.env.DEBUG === 'true' && sanitizedTitle !== title) {
|
||||
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);
|
||||
|
||||
if (req.body.title) {
|
||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
||||
const newTitle = sanitizeTitle(req.body.title);
|
||||
// Basic client-side validation - server will handle full sanitization
|
||||
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) {
|
||||
console.warn('Sanitized card title input:', req.body.title, '->', newTitle);
|
||||
|
|
|
|||
|
|
@ -314,13 +314,10 @@ Lists.helpers({
|
|||
|
||||
Lists.mutations({
|
||||
rename(title) {
|
||||
// Sanitize title on client side as well
|
||||
// Basic client-side validation - server will handle full sanitization
|
||||
if (typeof title === 'string') {
|
||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
||||
const sanitizedTitle = sanitizeTitle(title);
|
||||
if (process.env.DEBUG === 'true' && sanitizedTitle !== title) {
|
||||
console.warn('Client-side sanitized list title:', title, '->', sanitizedTitle);
|
||||
}
|
||||
// Basic length check to prevent abuse
|
||||
const sanitizedTitle = title.length > 1000 ? title.substring(0, 1000) : title;
|
||||
return { $set: { title: sanitizedTitle } };
|
||||
}
|
||||
return { $set: { title } };
|
||||
|
|
@ -654,8 +651,8 @@ if (Meteor.isServer) {
|
|||
|
||||
// Update title if provided
|
||||
if (req.body.title) {
|
||||
const { sanitizeTitle } = require('../server/lib/inputSanitizer');
|
||||
const newTitle = sanitizeTitle(req.body.title);
|
||||
// Basic client-side validation - server will handle full sanitization
|
||||
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) {
|
||||
console.warn('Sanitized list title input:', req.body.title, '->', newTitle);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue