import { getRelativePosition } from './custom.util.js' const EDITOR_CLASS = 'jsmind-editor' // jsmind class name const SUGGESTION_BOX_CLASS = 'jsmind-suggestions' const SUGGESTION_ITEM_CLASS = 'suggestion-item' /** * jsMind 搜尋管理 * jsMind Search Manager */ export class JsmindSearch { /** * 建構搜尋 * Constructor for search * @param {Object} jm - jsMind 實例 (jsMind instance) * @param {Function} searchAPI - 遠程搜尋 API 函式 (Remote search API function) * @param {string} tableUID */ constructor(jm, searchAPI, tableUID) { this.jm = jm this.searchAPI = searchAPI this.container = document.getElementById(jm.options.container) this.suggestionBox = null this.tableUID = tableUID this.init() } /** * 初始化搜尋事件 * Initialize search events */ init() { // 確保不會重複綁定 dblclick 事件 // Ensure double-click event is not bound multiple times this.container.removeEventListener('dblclick', this.onDoubleClick) this.container.addEventListener('dblclick', this.onDoubleClick.bind(this)) } /** * 處理雙擊事件以觸發搜尋 * Handle double-click event to trigger search * @param {Event} e - 事件對象 (Event object) */ onDoubleClick(e) { // 非可編輯狀態不執行 // Ignore if not editable if (!this.jm.options.editable) return const node = this.jm.get_selected_node() if (!node) return // 避免影響原生編輯功能,稍後執行 // Prevent interfering with native edit mode setTimeout(() => this.handleSearch(node), 100) } /** * 開始處理搜尋 * Start handling search * @param {Object} node - 當前選中節點 (Selected node) */ handleSearch(node) { const inputField = document.querySelector(`.${EDITOR_CLASS}`) if (!inputField) return // 確保不會重複綁定 input 事件 // Ensure input event is not bound multiple times inputField.removeEventListener('input', this.onInput) inputField.addEventListener('input', this.onInput.bind(this, node)) } /** * 處理使用者輸入 * Handle user input * @param {Object} node - 當前選中節點 (Selected node) * @param {Event} e - 輸入事件 (Input event) */ async onInput(node, e) { const query = e.target.value.trim() if (!query) return await new Promise(resolve => setTimeout(resolve, 500)); try { const results = await this.searchAPI(query, this.tableUID) this.showSuggestion(node, e.target, results) } catch (error) { // Search API error handling console.error('搜尋 API 錯誤:', error) } } /** * 顯示搜尋建議框 * Show search suggestion box * @param {Object} node - 當前選中節點 (Selected node) * @param {HTMLElement} inputElement - 輸入框 (Input field) * @param {Array} results - 搜尋結果 (Search results) */ showSuggestion(node, inputElement, results) { const container = this.container const nodeElement = inputElement.parentNode if (!nodeElement) return const { left, top, height } = getRelativePosition(nodeElement, container) this.suggestionBox = this.suggestionBox || this.createSuggestionBox() // 更新建議框內容 // Update suggestion box content this.suggestionBox.innerHTML = results .map( (item) => `