374 lines
13 KiB
JavaScript
Raw Normal View History

2025-06-10 07:19:47 +08:00
/**
* MCP Feedback Enhanced - 工具模組
* ================================
*
* 提供共用的工具函數和常數定義
*/
(function() {
'use strict';
// 確保命名空間存在
window.MCPFeedback = window.MCPFeedback || {};
2025-06-13 06:47:11 +08:00
window.MCPFeedback.Utils = window.MCPFeedback.Utils || {};
2025-06-10 07:19:47 +08:00
/**
2025-06-13 06:47:11 +08:00
* 工具函數模組 - 擴展現有的 Utils 物件
2025-06-10 07:19:47 +08:00
*/
2025-06-13 06:47:11 +08:00
Object.assign(window.MCPFeedback.Utils, {
2025-06-10 07:19:47 +08:00
/**
* 格式化檔案大小
* @param {number} bytes - 位元組數
* @returns {string} 格式化後的檔案大小
*/
formatFileSize: function(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
/**
* 生成唯一 ID
* @param {string} prefix - ID 前綴
* @returns {string} 唯一 ID
*/
generateId: function(prefix) {
prefix = prefix || 'id';
return prefix + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
},
/**
* 深度複製物件
* @param {Object} obj - 要複製的物件
* @returns {Object} 複製後的物件
*/
deepClone: function(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => this.deepClone(item));
if (typeof obj === 'object') {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = this.deepClone(obj[key]);
}
}
return clonedObj;
}
},
/**
* 防抖函數
* @param {Function} func - 要防抖的函數
* @param {number} wait - 等待時間毫秒
* @returns {Function} 防抖後的函數
*/
debounce: function(func, wait) {
let timeout;
return function executedFunction() {
const later = () => {
clearTimeout(timeout);
func.apply(this, arguments);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
/**
* 節流函數
* @param {Function} func - 要節流的函數
* @param {number} limit - 限制時間毫秒
* @returns {Function} 節流後的函數
*/
throttle: function(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
/**
* 安全的 JSON 解析
* @param {string} jsonString - JSON 字串
* @param {*} defaultValue - 預設值
* @returns {*} 解析結果或預設值
*/
safeJsonParse: function(jsonString, defaultValue) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.warn('JSON 解析失敗:', error);
return defaultValue;
}
},
/**
* 檢查元素是否存在
* @param {string} selector - CSS 選擇器
* @returns {boolean} 元素是否存在
*/
elementExists: function(selector) {
return document.querySelector(selector) !== null;
},
2025-06-14 11:38:14 +08:00
/**
* 從右側截斷路徑保留最後幾個目錄層級
* @param {string} path - 完整路徑
* @param {number} maxLevels - 保留的最大目錄層級數默認2
* @param {number} maxLength - 最大顯示長度默認40
* @returns {object} 包含 truncated截斷後的路徑 isTruncated是否被截斷
*/
truncatePathFromRight: function(path, maxLevels, maxLength) {
maxLevels = maxLevels || 2;
maxLength = maxLength || 40;
if (!path || typeof path !== 'string') {
return { truncated: path || '', isTruncated: false };
}
// 如果路徑長度小於最大長度,直接返回
if (path.length <= maxLength) {
return { truncated: path, isTruncated: false };
}
// 統一路徑分隔符為反斜線Windows風格
const normalizedPath = path.replace(/\//g, '\\');
// 分割路徑
const parts = normalizedPath.split('\\').filter(part => part.length > 0);
if (parts.length <= maxLevels) {
return { truncated: normalizedPath, isTruncated: false };
}
// 取最後幾個層級
const lastParts = parts.slice(-maxLevels);
const truncatedPath = '...' + '\\' + lastParts.join('\\');
return {
truncated: truncatedPath,
isTruncated: true
};
},
/**
* 複製文字到剪貼板統一的複製功能
* @param {string} text - 要複製的文字
* @param {string} successMessage - 成功提示訊息
* @param {string} errorMessage - 錯誤提示訊息
* @returns {Promise<boolean>} 複製是否成功
*/
copyToClipboard: function(text, successMessage, errorMessage) {
successMessage = successMessage || '已複製到剪貼板';
errorMessage = errorMessage || '複製失敗';
return new Promise(function(resolve) {
if (navigator.clipboard && navigator.clipboard.writeText) {
// 使用現代 Clipboard API
navigator.clipboard.writeText(text).then(function() {
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
window.MCPFeedback.Utils.showMessage(successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS);
}
resolve(true);
}).catch(function(err) {
console.error('Clipboard API 複製失敗:', err);
// 回退到舊方法
const success = window.MCPFeedback.Utils.fallbackCopyToClipboard(text);
if (success) {
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
window.MCPFeedback.Utils.showMessage(successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS);
}
resolve(true);
} else {
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
window.MCPFeedback.Utils.showMessage(errorMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_ERROR);
}
resolve(false);
}
});
} else {
// 直接使用回退方法
const success = window.MCPFeedback.Utils.fallbackCopyToClipboard(text);
if (success) {
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
window.MCPFeedback.Utils.showMessage(successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS);
}
resolve(true);
} else {
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
window.MCPFeedback.Utils.showMessage(errorMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_ERROR);
}
resolve(false);
}
}
});
},
/**
* 回退的複製到剪貼板方法
* @param {string} text - 要複製的文字
* @returns {boolean} 複製是否成功
*/
fallbackCopyToClipboard: function(text) {
try {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
return successful;
} catch (err) {
console.error('回退複製方法失敗:', err);
return false;
}
},
2025-06-10 07:19:47 +08:00
/**
* 安全的元素查詢
* @param {string} selector - CSS 選擇器
* @param {Element} context - 查詢上下文可選
* @returns {Element|null} 找到的元素或 null
*/
safeQuerySelector: function(selector, context) {
try {
const root = context || document;
return root.querySelector(selector);
} catch (error) {
console.warn('元素查詢失敗:', selector, error);
return null;
}
},
/**
* 顯示訊息提示
* @param {string} message - 訊息內容
* @param {string} type - 訊息類型 (success, error, warning, info)
* @param {number} duration - 顯示時間毫秒
*/
showMessage: function(message, type, duration) {
type = type || 'info';
duration = duration || 3000;
// 創建訊息元素
const messageDiv = document.createElement('div');
messageDiv.className = 'message message-' + type;
messageDiv.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
z-index: 1001;
padding: 12px 20px;
background: var(--${type === 'error' ? 'error' : type === 'warning' ? 'warning' : 'success'}-color, #4CAF50);
color: white;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
max-width: 300px;
word-wrap: break-word;
transition: opacity 0.3s ease;
`;
messageDiv.textContent = message;
document.body.appendChild(messageDiv);
// 自動移除
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.style.opacity = '0';
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.parentNode.removeChild(messageDiv);
}
}, 300);
}
}, duration);
},
/**
* 檢查 WebSocket 是否可用
* @returns {boolean} WebSocket 是否可用
*/
isWebSocketSupported: function() {
return 'WebSocket' in window;
},
/**
* 檢查 localStorage 是否可用
* @returns {boolean} localStorage 是否可用
*/
isLocalStorageSupported: function() {
try {
const test = '__localStorage_test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
},
2025-06-13 13:43:27 +08:00
/**
* HTML 轉義函數
* @param {string} text - 要轉義的文字
* @returns {string} 轉義後的文字
*/
escapeHtml: function(text) {
if (typeof text !== 'string') {
return text;
}
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
},
2025-06-10 07:19:47 +08:00
/**
* 常數定義
*/
CONSTANTS: {
// WebSocket 狀態
WS_CONNECTING: 0,
WS_OPEN: 1,
WS_CLOSING: 2,
WS_CLOSED: 3,
// 回饋狀態
FEEDBACK_WAITING: 'waiting_for_feedback',
FEEDBACK_SUBMITTED: 'feedback_submitted',
FEEDBACK_PROCESSING: 'processing',
// 預設設定
DEFAULT_HEARTBEAT_FREQUENCY: 30000,
DEFAULT_TAB_HEARTBEAT_FREQUENCY: 5000,
DEFAULT_RECONNECT_DELAY: 1000,
MAX_RECONNECT_ATTEMPTS: 5,
TAB_EXPIRED_THRESHOLD: 30000,
// 訊息類型
MESSAGE_SUCCESS: 'success',
MESSAGE_ERROR: 'error',
MESSAGE_WARNING: 'warning',
MESSAGE_INFO: 'info'
}
2025-06-13 06:47:11 +08:00
});
2025-06-10 07:19:47 +08:00
console.log('✅ Utils 模組載入完成');
})();