diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index e25ebcd..caadb01 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -207,19 +207,22 @@ // 11. 初始化自動提交管理器 self.initializeAutoSubmitManager(); - // 11. 應用設定到 UI + // 12. 初始化 Textarea 高度管理器 + self.initializeTextareaHeightManager(); + + // 13. 應用設定到 UI self.settingsManager.applyToUI(); - // 12. 初始化各個管理器 + // 14. 初始化各個管理器 self.uiManager.initTabs(); self.imageHandler.init(); - // 13. 檢查並啟動自動提交(如果條件滿足) + // 15. 檢查並啟動自動提交(如果條件滿足) setTimeout(function() { self.checkAndStartAutoSubmit(); }, 500); // 延遲 500ms 確保所有初始化完成 - // 14. 建立 WebSocket 連接 + // 16. 建立 WebSocket 連接 self.webSocketManager.connect(); resolve(); @@ -473,6 +476,47 @@ } }; + /** + * 初始化 Textarea 高度管理器 + */ + FeedbackApp.prototype.initializeTextareaHeightManager = function() { + console.log('📏 初始化 Textarea 高度管理器...'); + + try { + // 檢查 TextareaHeightManager 模組是否已載入 + if (!window.MCPFeedback.TextareaHeightManager) { + console.warn('⚠️ TextareaHeightManager 模組未載入,跳過初始化'); + return; + } + + // 建立 TextareaHeightManager 實例 + this.textareaHeightManager = new window.MCPFeedback.TextareaHeightManager({ + settingsManager: this.settingsManager, + debounceDelay: 500 // 500ms 防抖延遲 + }); + + // 初始化管理器 + this.textareaHeightManager.initialize(); + + // 註冊 combinedFeedbackText textarea + const success = this.textareaHeightManager.registerTextarea( + 'combinedFeedbackText', + 'combinedFeedbackTextHeight' + ); + + if (success) { + console.log('✅ combinedFeedbackText 高度管理已啟用'); + } else { + console.warn('⚠️ combinedFeedbackText 註冊失敗'); + } + + console.log('✅ Textarea 高度管理器初始化完成'); + + } catch (error) { + console.error('❌ Textarea 高度管理器初始化失敗:', error); + } + }; + /** * 處理 WebSocket 開啟 */ @@ -1451,7 +1495,9 @@ this.imageHandler.cleanup(); } - + if (this.textareaHeightManager) { + this.textareaHeightManager.destroy(); + } console.log('✅ 應用程式資源清理完成'); }; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js index 3524810..1ad2549 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js @@ -40,7 +40,9 @@ sessionHistoryRetentionHours: 72, // 用戶訊息記錄設定 userMessageRecordingEnabled: true, - userMessagePrivacyLevel: 'full' // 'full', 'basic', 'disabled' + userMessagePrivacyLevel: 'full', // 'full', 'basic', 'disabled' + // UI 元素尺寸設定 + combinedFeedbackTextHeight: 150 // combinedFeedbackText textarea 的高度(px) }; // 當前設定 diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/textarea-height-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/textarea-height-manager.js new file mode 100644 index 0000000..a673e76 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/js/modules/textarea-height-manager.js @@ -0,0 +1,267 @@ +/** + * Textarea 高度管理器 + * 負責監聽 textarea 高度變化並持久化設定 + */ + +(function() { + 'use strict'; + + // 確保命名空間存在 + window.MCPFeedback = window.MCPFeedback || {}; + const Utils = window.MCPFeedback.Utils; + + /** + * TextareaHeightManager 建構函數 + */ + function TextareaHeightManager(options) { + options = options || {}; + + // 設定管理器實例 + this.settingsManager = options.settingsManager || null; + + // 已註冊的 textarea 元素 + this.registeredTextareas = new Map(); + + // ResizeObserver 實例 + this.resizeObserver = null; + + // 防抖計時器 + this.debounceTimers = new Map(); + + // 防抖延遲(毫秒) + this.debounceDelay = options.debounceDelay || 500; + + console.log('📏 TextareaHeightManager 建構函數初始化完成'); + } + + /** + * 初始化高度管理器 + */ + TextareaHeightManager.prototype.initialize = function() { + console.log('📏 開始初始化 TextareaHeightManager...'); + + // 檢查 ResizeObserver 支援 + if (!window.ResizeObserver) { + console.warn('📏 瀏覽器不支援 ResizeObserver,將使用備用方案'); + this.initializeFallback(); + return; + } + + // 建立 ResizeObserver + this.createResizeObserver(); + + console.log('✅ TextareaHeightManager 初始化完成'); + }; + + /** + * 建立 ResizeObserver + */ + TextareaHeightManager.prototype.createResizeObserver = function() { + const self = this; + + this.resizeObserver = new ResizeObserver(function(entries) { + entries.forEach(function(entry) { + const element = entry.target; + const config = self.registeredTextareas.get(element); + + if (config) { + self.handleResize(element, config); + } + }); + }); + + console.log('📏 ResizeObserver 建立完成'); + }; + + /** + * 處理 textarea 尺寸變化 + */ + TextareaHeightManager.prototype.handleResize = function(element, config) { + const self = this; + const settingKey = config.settingKey; + + // 清除之前的防抖計時器 + if (this.debounceTimers.has(settingKey)) { + clearTimeout(this.debounceTimers.get(settingKey)); + } + + // 設定新的防抖計時器 + const timer = setTimeout(function() { + const currentHeight = element.offsetHeight; + + // 檢查高度是否有變化 + if (currentHeight !== config.lastHeight) { + console.log('📏 偵測到 ' + settingKey + ' 高度變化:', config.lastHeight + 'px → ' + currentHeight + 'px'); + + // 更新記錄的高度 + config.lastHeight = currentHeight; + + // 保存到設定 + if (self.settingsManager) { + self.settingsManager.set(settingKey, currentHeight); + } + } + + // 清除計時器記錄 + self.debounceTimers.delete(settingKey); + }, this.debounceDelay); + + this.debounceTimers.set(settingKey, timer); + }; + + /** + * 註冊 textarea 元素 + */ + TextareaHeightManager.prototype.registerTextarea = function(elementId, settingKey) { + const element = Utils.safeQuerySelector('#' + elementId); + + if (!element) { + console.warn('📏 找不到元素:', elementId); + return false; + } + + if (element.tagName.toLowerCase() !== 'textarea') { + console.warn('📏 元素不是 textarea:', elementId); + return false; + } + + // 載入並應用保存的高度 + this.loadAndApplyHeight(element, settingKey); + + // 建立配置物件 + const config = { + elementId: elementId, + settingKey: settingKey, + lastHeight: element.offsetHeight + }; + + // 註冊到 Map + this.registeredTextareas.set(element, config); + + // 開始監聽 + if (this.resizeObserver) { + this.resizeObserver.observe(element); + } + + console.log('📏 已註冊 textarea:', elementId, '設定鍵:', settingKey); + return true; + }; + + /** + * 載入並應用保存的高度 + */ + TextareaHeightManager.prototype.loadAndApplyHeight = function(element, settingKey) { + if (!this.settingsManager) { + console.warn('📏 沒有設定管理器,無法載入高度設定'); + return; + } + + const savedHeight = this.settingsManager.get(settingKey); + + if (savedHeight && typeof savedHeight === 'number' && savedHeight > 0) { + // 確保不小於最小高度 + const minHeight = this.getMinHeight(element); + const finalHeight = Math.max(savedHeight, minHeight); + + // 應用高度 + element.style.height = finalHeight + 'px'; + + console.log('📏 已恢復 ' + settingKey + ' 高度:', finalHeight + 'px'); + } else { + console.log('📏 沒有找到 ' + settingKey + ' 的保存高度,使用預設值'); + } + }; + + /** + * 獲取元素的最小高度 + */ + TextareaHeightManager.prototype.getMinHeight = function(element) { + const computedStyle = window.getComputedStyle(element); + const minHeight = computedStyle.minHeight; + + if (minHeight && minHeight !== 'none') { + const value = parseInt(minHeight); + if (!isNaN(value)) { + return value; + } + } + + // 預設最小高度 + return 150; + }; + + /** + * 取消註冊 textarea 元素 + */ + TextareaHeightManager.prototype.unregisterTextarea = function(elementId) { + const element = Utils.safeQuerySelector('#' + elementId); + + if (!element) { + return false; + } + + const config = this.registeredTextareas.get(element); + + if (config) { + // 停止監聽 + if (this.resizeObserver) { + this.resizeObserver.unobserve(element); + } + + // 清除防抖計時器 + if (this.debounceTimers.has(config.settingKey)) { + clearTimeout(this.debounceTimers.get(config.settingKey)); + this.debounceTimers.delete(config.settingKey); + } + + // 從 Map 中移除 + this.registeredTextareas.delete(element); + + console.log('📏 已取消註冊 textarea:', elementId); + return true; + } + + return false; + }; + + /** + * 備用方案初始化(當不支援 ResizeObserver 時) + */ + TextareaHeightManager.prototype.initializeFallback = function() { + console.log('📏 使用備用方案初始化...'); + + // 備用方案可以使用 MutationObserver 或定期檢查 + // 這裡先實作基本功能,主要是載入保存的高度 + console.log('📏 備用方案初始化完成(僅支援載入功能)'); + }; + + /** + * 銷毀管理器 + */ + TextareaHeightManager.prototype.destroy = function() { + console.log('📏 開始銷毀 TextareaHeightManager...'); + + // 清除所有防抖計時器 + this.debounceTimers.forEach(function(timer) { + clearTimeout(timer); + }); + this.debounceTimers.clear(); + + // 停止所有監聽 + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + + // 清除註冊記錄 + this.registeredTextareas.clear(); + + console.log('✅ TextareaHeightManager 銷毀完成'); + }; + + // 將 TextareaHeightManager 加入命名空間 + window.MCPFeedback.TextareaHeightManager = TextareaHeightManager; + + console.log('✅ TextareaHeightManager 模組載入完成'); + +})(); diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index 6860100..de223d7 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -1153,6 +1153,7 @@ +