|
@@ -25,7 +25,6 @@
|
|
|
</div>
|
|
|
<div class="text-text-secondary flex-1 overflow-y-auto py-1 text-[13px]">
|
|
|
<div
|
|
|
- id="textarea"
|
|
|
class="break-all"
|
|
|
style="line-height: 1.5"
|
|
|
ref="ref_textarea"
|
|
@@ -80,7 +79,6 @@
|
|
|
</div>
|
|
|
<div class="text-text-secondary flex-1 py-0 text-xs">
|
|
|
<div
|
|
|
- id="textarea"
|
|
|
class="break-all"
|
|
|
style="line-height: 1.2"
|
|
|
ref="ref_textarea"
|
|
@@ -225,88 +223,8 @@ const handleInput = (e) => {
|
|
|
newSelection.addRange(newRange)
|
|
|
}
|
|
|
state.textCount = e.target.innerText?.trim().length || 0
|
|
|
- isSlashBeforeCursorEnhanced()
|
|
|
-
|
|
|
emitValue()
|
|
|
}
|
|
|
-function isSlashBeforeCursorEnhanced() {
|
|
|
- const selection = window.getSelection()
|
|
|
- if (selection.rangeCount === 0) return false
|
|
|
-
|
|
|
- const range = selection.getRangeAt(0)
|
|
|
- let node = range.startContainer
|
|
|
- let offset = range.startOffset
|
|
|
-
|
|
|
- // 如果当前节点不是文本节点,尝试找到前一个文本节点
|
|
|
- if (node.nodeType !== Node.TEXT_NODE) {
|
|
|
- // 如果光标在元素开始处,需要查找前一个兄弟节点
|
|
|
- if (offset === 0) {
|
|
|
- let prevNode = getPreviousTextNode(node)
|
|
|
- if (!prevNode) return false
|
|
|
- node = prevNode
|
|
|
- offset = node.textContent.length
|
|
|
- } else {
|
|
|
- // 光标在元素中间,需要检查子节点
|
|
|
- const child = node.childNodes[offset - 1]
|
|
|
- const lastTextNode = getLastTextNode(child)
|
|
|
- if (!lastTextNode) return false
|
|
|
- node = lastTextNode
|
|
|
- offset = node.textContent.length
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 检查前一个字符是否是/
|
|
|
- if (offset > 0) {
|
|
|
- if (node.textContent[offset - 1] === '/') {
|
|
|
- showVariablePopup(range)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果当前文本节点开头,需要检查前一个文本节点
|
|
|
- const prevTextNode = getPreviousTextNode(node)
|
|
|
- if (prevTextNode && prevTextNode.textContent.length > 0) {
|
|
|
- if (prevTextNode.textContent[prevTextNode.textContent.length - 1] === '/') {
|
|
|
- showVariablePopup(range)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- state.showVariableList = false
|
|
|
-}
|
|
|
-// 辅助函数:获取前一个文本节点
|
|
|
-function getPreviousTextNode(node) {
|
|
|
- let sibling = node.previousSibling
|
|
|
- while (sibling) {
|
|
|
- if (
|
|
|
- sibling.nodeType === Node.TEXT_NODE &&
|
|
|
- sibling.textContent.trim() !== ''
|
|
|
- ) {
|
|
|
- return sibling
|
|
|
- }
|
|
|
- if (
|
|
|
- sibling.nodeType === Node.ELEMENT_NODE &&
|
|
|
- sibling.childNodes.length > 0
|
|
|
- ) {
|
|
|
- const lastChild = getLastTextNode(sibling)
|
|
|
- if (lastChild) return lastChild
|
|
|
- }
|
|
|
- sibling = sibling.previousSibling
|
|
|
- }
|
|
|
- return node.parentNode ? getPreviousTextNode(node.parentNode) : null
|
|
|
-}
|
|
|
-
|
|
|
-// 辅助函数:获取最后一个文本节点
|
|
|
-function getLastTextNode(node) {
|
|
|
- if (node.nodeType === Node.TEXT_NODE) return node
|
|
|
- if (node.nodeType === Node.ELEMENT_NODE && node.childNodes.length > 0) {
|
|
|
- for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
|
- const result = getLastTextNode(node.childNodes[i])
|
|
|
- if (result) return result
|
|
|
- }
|
|
|
- }
|
|
|
- return null
|
|
|
-}
|
|
|
const handlePaste = (e) => {
|
|
|
e.preventDefault()
|
|
|
const text = (e.clipboardData || window.clipboardData).getData('text')
|
|
@@ -382,21 +300,7 @@ const emitValue = () => {
|
|
|
emit('update:modelValue', newDom.innerHTML)
|
|
|
}
|
|
|
}
|
|
|
-// 获取光标位置
|
|
|
-const getCaretPosition = () => {
|
|
|
- const selection: any = window.getSelection()
|
|
|
- if (selection.rangeCount === 0) return null
|
|
|
|
|
|
- const range = selection.getRangeAt(0)
|
|
|
- const preCaretRange = range.cloneRange()
|
|
|
- preCaretRange.selectNodeContents(ref_textarea.value)
|
|
|
- preCaretRange.setEnd(range.endContainer, range.endOffset)
|
|
|
-
|
|
|
- return {
|
|
|
- offset: preCaretRange.toString().length,
|
|
|
- range: range,
|
|
|
- }
|
|
|
-}
|
|
|
// 显示变量弹窗
|
|
|
const showVariablePopup = (range) => {
|
|
|
const rect = range.getBoundingClientRect()
|
|
@@ -420,7 +324,7 @@ const optionsCpt = computed(() => {
|
|
|
return state.options
|
|
|
}
|
|
|
return state.options
|
|
|
- .map((v) => {
|
|
|
+ .map((v: any) => {
|
|
|
const obj = { ...v }
|
|
|
obj.options = obj.options.filter(
|
|
|
(s) => s.label.includes(state.text) || s.key.includes(state.text),
|
|
@@ -431,12 +335,22 @@ const optionsCpt = computed(() => {
|
|
|
})
|
|
|
onMounted(() => {
|
|
|
if (!props.readonly && props.modelValue) {
|
|
|
- ref_textarea.value.innerHTML = props.modelValue
|
|
|
- ref_textarea.value.dispatchEvent(
|
|
|
- new Event('input', {
|
|
|
- bubbles: true,
|
|
|
- cancelable: true,
|
|
|
- }),
|
|
|
+ // ref_textarea.value.innerHTML = props.modelValue
|
|
|
+ // ref_textarea.value.dispatchEvent(
|
|
|
+ // new Event('input', {
|
|
|
+ // bubbles: true,
|
|
|
+ // cancelable: true,
|
|
|
+ // }),
|
|
|
+ // )
|
|
|
+ ref_textarea.value.innerHTML = props.modelValue.replace(
|
|
|
+ /\{\{#([^#]+)#\}\}/g,
|
|
|
+ (match, p1) => {
|
|
|
+ const k = p1.split('_')
|
|
|
+ const vars = state.optionsMap.get(`${k[0]}_${k[1]}`)
|
|
|
+ const dom = document.createElement('div')
|
|
|
+ dom.appendChild(initVarsDom(vars))
|
|
|
+ return dom.innerHTML
|
|
|
+ },
|
|
|
)
|
|
|
}
|
|
|
if (!props.readonly) {
|