From 374764d6f2c64129ae940e685e7b693e34922560 Mon Sep 17 00:00:00 2001 From: jin Date: Sun, 29 Dec 2024 16:25:39 +0300 Subject: [PATCH] $mol_dom_point, $mol_dom_range, $mol_dom = $mol_dom_context --- dom/dom.ts | 5 +++ dom/point/point.ts | 106 +++++++++++++++++++++++++++++++++++++++++++++ dom/range/range.ts | 61 ++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 dom/dom.ts create mode 100644 dom/point/point.ts create mode 100644 dom/range/range.ts diff --git a/dom/dom.ts b/dom/dom.ts new file mode 100644 index 0000000000..2a29b3223a --- /dev/null +++ b/dom/dom.ts @@ -0,0 +1,5 @@ +namespace $ { + + export var $mol_dom = $mol_dom_context + +} diff --git a/dom/point/point.ts b/dom/point/point.ts new file mode 100644 index 0000000000..4cb5ad3261 --- /dev/null +++ b/dom/point/point.ts @@ -0,0 +1,106 @@ +namespace $ { + + export class $mol_dom_point extends Object { + + constructor( + public node: Node, + public pos: number, + ) { super() } + + static start( node: Node ) { + return new this( node, 0 ) + } + + static end( node: Node ) { + const length = node.nodeValue?.length ?? node.childNodes.length + return new this( node, length ) + } + + to( point: $mol_dom_point ) { + this.node = point.node + this.pos = point.pos + } + + to_start() { + this.pos = 0 + return this + } + + to_end() { + this.pos = this.node.nodeValue?.length ?? this.node.childNodes.length + return this + } + + is_start() { + return this.pos <= 0 + } + + is_end() { + return this.pos >= ( this.node.nodeValue?.length ?? this.node.childNodes.length ) + } + + char_backward( root: Element ): $mol_dom_point { + return this.backward( ()=> { + if( this.node === root && this.is_start() ) return true + if( this.node.nodeType !== this.node.TEXT_NODE ) return false + if( this.is_start() ) return false + this.pos -= 1 + return true + } ) + } + + char_forward( root: Element ): $mol_dom_point { + return this.forward( ()=> { + if( this.node === root && this.is_end() ) return true + if( this.node.nodeType !== this.node.TEXT_NODE ) return false + if( this.is_end() ) return false + this.pos += 1 + return true + } ) + } + + backward( check: ()=> boolean ): $mol_dom_point { + + if( check() ) return this + + if( !this.is_start() ) { + const kid = this.node.childNodes[ this.pos - 1 ] + this.node = kid + this.to_end() + return this.backward( check ) + } + + const parent = this.node.parentElement + if( !parent ) return this + + const offset = [ ... parent.childNodes ].indexOf( this.node as ChildNode ) + this.node = parent + this.pos = offset + return this.backward( check ) + + } + + forward( check: ()=> boolean ): $mol_dom_point { + + if( check() ) return this + + if( !this.is_end() ) { + const kid = this.node.childNodes[ this.pos ] + this.node = kid + this.to_start() + return this.forward( check ) + } + + const parent = this.node.parentElement + if( !parent ) return this + + const offset = [ ... parent.childNodes ].indexOf( this.node as ChildNode ) + 1 + this.node = parent + this.pos = offset + return this.forward( check ) + + } + + } + +} diff --git a/dom/range/range.ts b/dom/range/range.ts new file mode 100644 index 0000000000..e4a2ff8081 --- /dev/null +++ b/dom/range/range.ts @@ -0,0 +1,61 @@ +namespace $ { + export class $mol_dom_range extends Object { + + constructor( + readonly head: $mol_dom_point, + readonly foot: $mol_dom_point, + ) { super() } + + static from_selection( sel = $mol_dom_context.getSelection()! ) { + return this.from_native( sel?.getRangeAt(0) ) + } + + static from_native( range: Range ) { + return new this( + new $mol_dom_point( range.startContainer, range.startOffset ), + new $mol_dom_point( range.endContainer, range.endOffset ), + ) + } + + static inside( node: Node ) { + return new this( + $mol_dom_point.start( node ), + $mol_dom_point.end( node ), + ) + } + + static around( node: Node ) { + + const parent = node.parentNode! + const pos = [ ... parent.childNodes ].indexOf( node as ChildNode ) + + return new this( + new $mol_dom_point( parent, pos ), + new $mol_dom_point( parent, pos + 1 ), + ) + + } + + is_empty() { + return this.head.node === this.foot.node && this.head.pos === this.foot.pos + } + + clear() { + this.native().deleteContents() + } + + select() { + const sel = $mol_dom_context.document.getSelection()! + sel.removeAllRanges() + sel.addRange( this.native() ) + } + + native() { + const range = $mol_dom_context.document.createRange() + range.setEnd( this.foot.node, this.foot.pos ) + range.setStart( this.head.node, this.head.pos ) + return range + } + + } +}