universal_table/app/assets/javascripts/mind_map/utils/custom.search.js

165 lines
5.3 KiB
JavaScript

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) =>
`<div class="${SUGGESTION_ITEM_CLASS}" data-link="${item.link}" data-text="${item.text}">${item.text}</div>`
)
.join('')
this.suggestionBox.style.left = `${left}px`
this.suggestionBox.style.top = `${top + height}px`
this.suggestionBox.style.display = 'block'
// 綁定建議點擊事件
// Bind suggestion click events
document.querySelectorAll(`.${SUGGESTION_ITEM_CLASS}`).forEach((item) => {
item.removeEventListener('mousedown', this.onSuggestionClick)
item.addEventListener('mousedown', this.onSuggestionClick.bind(this, node))
})
}
/**
* 建立搜尋建議框
* Create search suggestion box
* @returns {HTMLElement} - 建議框 DOM (Suggestion box DOM)
*/
createSuggestionBox() {
let suggestionBox = document.getElementById(SUGGESTION_BOX_CLASS)
if (!suggestionBox) {
suggestionBox = document.createElement('div')
suggestionBox.classList.add(SUGGESTION_BOX_CLASS)
this.container.appendChild(suggestionBox)
}
return suggestionBox
}
/**
* 處理點擊建議
* Handle suggestion click
* @param {Object} node - 當前選中節點 (Selected node)
* @param {Event} e - 點擊事件 (Click event)
*/
onSuggestionClick(node, e) {
e.preventDefault()
const text = e.target.getAttribute('data-text')
const link = e.target.getAttribute('data-link')
node.data.text = text
node.data.link = link
this.jm.end_edit()
this.jm.update_node(node.id, text)
// 選擇後隱藏建議框
// Hide suggestions after selection
this.suggestionBox.style.display = 'none'
}
}