165 lines
5.3 KiB
JavaScript
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'
|
|
}
|
|
}
|