Эх сурвалжийг харах

fix(pattern): respect hard_break boundaries in pattern detection

Replace textContent-based text extraction with node-aware iteration
that resets textBefore on hard_break nodes. This fixes pattern detection
on new lines where textContent incorrectly concatenated content across
line breaks, causing patterns on line 2+ to never match.

Fixes pattern session always being null on multi-line blocks.
Zander Hawke 1 сар өмнө
parent
commit
3a1e522c87

+ 50 - 7
packages/editor/src/composables/usePatternPlugin.ts

@@ -105,11 +105,39 @@ function findPattern(
   prevSession: PatternSession | null,
 ): PatternSession | null {
   const { $head } = state.selection
-  if (!$head.parent.isTextblock) return null
+  const parentNode = $head.parent
+  if (!parentNode || !parentNode.isTextblock) return null
+
+  // Build layout-aware textBefore by iterating nodes
+  // This respects hard_break boundaries instead of flattening via textContent
+  let textBefore = ""
+  let currentOffset = 0
+  const targetOffset = $head.parentOffset
+
+  for (let i = 0; i < parentNode.childCount; i++) {
+    const child = parentNode.child(i)
+
+    if (currentOffset >= targetOffset) break
+
+    if (child.type.name === "hard_break") {
+      // Hard break resets the current line
+      textBefore = ""
+      currentOffset += child.nodeSize
+    } else if (child.isText && child.text) {
+      const remainingSpace = targetOffset - currentOffset
+      if (remainingSpace <= child.text.length) {
+        textBefore += child.text.slice(0, remainingSpace)
+        break
+      } else {
+        textBefore += child.text
+        currentOffset += child.text.length
+      }
+    } else {
+      currentOffset += child.nodeSize
+    }
+  }
 
-  const text = $head.parent.textContent
-  const offset = $head.parentOffset
-  const textBefore = text.slice(0, offset)
+  const offset = textBefore.length
 
   for (const spec of PATTERN_SPECS) {
     const match = matchPattern(textBefore, spec)
@@ -117,15 +145,30 @@ function findPattern(
 
     const { startIdx, trigger, query } = match
     const startPos = $head.start() + startIdx
-
     if (isPatternCompleted(spec, query)) return null
 
     // For delimiter-terminated patterns, check if the closing delimiter
-    // exists anywhere in the full textblock after the trigger start.
+    // exists anywhere in the textblock after the trigger start.
     // This prevents an active session when the pattern is already complete
     // but the cursor is inside it (e.g. navigating into [[Page Ref]]).
     if (spec.termination === "delimiter" && spec.end) {
-      const textFromTrigger = text.slice(startIdx)
+      // Build text from trigger position to end of textblock, respecting hard_breaks
+      let textFromTrigger = ""
+      let triggerFound = false
+      for (let i = 0; i < parentNode.childCount; i++) {
+        const child = parentNode.child(i)
+        if (child.type.name === "hard_break") {
+          textFromTrigger += "\n"
+        } else if (child.isText && child.text) {
+          if (triggerFound) {
+            textFromTrigger += child.text
+          } else if (child.text.includes(trigger)) {
+            triggerFound = true
+            const triggerIdx = child.text.indexOf(trigger)
+            textFromTrigger = child.text.slice(triggerIdx + trigger.length)
+          }
+        }
+      }
       if (textFromTrigger.includes(spec.end)) return null
     }