summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/utils/dom.ts
blob: 4f3724c7c950bf8a42fbb1ca2e06447c670d73e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
export function getCharacterOffsetInLine(lineElement: Element, targetNode: Node, offset: number): number {
  const r = document.createRange()
  r.selectNodeContents(lineElement)
  r.setEnd(targetNode, offset)
  return r.toString().length
}

export function getNodeOffsetInLine(lineElement: Element, charIndex: number): { node: Node; offset: number } | null {
  const walker = document.createTreeWalker(lineElement, NodeFilter.SHOW_TEXT, null)
  let remaining = Math.max(0, charIndex)
  let lastText: Node | null = null
  let lastLen = 0
  let node: Node | null
  while ((node = walker.nextNode())) {
    const len = node.textContent?.length || 0
    lastText = node
    lastLen = len
    if (remaining <= len) return { node, offset: remaining }
    remaining -= len
  }
  if (lastText) return { node: lastText, offset: lastLen }
  if (lineElement.firstChild) return { node: lineElement.firstChild, offset: 0 }
  return null
}

export function getSelectionInContainer(
  container: HTMLElement,
): { sl: number; sch: number; el: number; ech: number } | null {
  const s = window.getSelection()
  if (!s || s.rangeCount === 0) return null
  const r = s.getRangeAt(0)
  const sc = r.startContainer
  const ec = r.endContainer
  const getLineElement = (n: Node) =>
    (n.nodeType === Node.TEXT_NODE ? (n.parentElement as Element) : (n as Element))?.closest(".line")
  const sle = getLineElement(sc)
  const ele = getLineElement(ec)
  if (!sle || !ele) return null
  if (!container.contains(sle as Node) || !container.contains(ele as Node)) return null
  const cc = container.querySelector("code") as HTMLElement | null
  if (!cc) return null
  const lines = Array.from(cc.querySelectorAll(".line"))
  const sli = lines.indexOf(sle as Element)
  const eli = lines.indexOf(ele as Element)
  if (sli === -1 || eli === -1) return null
  const sl = sli + 1
  const el = eli + 1
  const sch = getCharacterOffsetInLine(sle as Element, sc, r.startOffset)
  const ech = getCharacterOffsetInLine(ele as Element, ec, r.endOffset)
  return { sl, sch, el, ech }
}