1+ import { MarkdownEditorInput , MarkdownEditorInputSelection } from "./interface" ;
2+ import { MarkdownEditorShortcutMap } from "../shortcuts" ;
3+ import { MarkdownEditorEventMap } from "../dom-handlers" ;
4+
5+
6+ export class TextareaInput implements MarkdownEditorInput {
7+
8+ protected input : HTMLTextAreaElement ;
9+ protected shortcuts : MarkdownEditorShortcutMap ;
10+ protected events : MarkdownEditorEventMap ;
11+
12+ constructor ( input : HTMLTextAreaElement , shortcuts : MarkdownEditorShortcutMap , events : MarkdownEditorEventMap ) {
13+ this . input = input ;
14+ this . shortcuts = shortcuts ;
15+ this . events = events ;
16+
17+ this . onKeyDown = this . onKeyDown . bind ( this ) ;
18+ this . configureListeners ( ) ;
19+ }
20+
21+ configureListeners ( ) : void {
22+ // TODO - Teardown handling
23+ this . input . addEventListener ( 'keydown' , this . onKeyDown ) ;
24+
25+ for ( const [ name , listener ] of Object . entries ( this . events ) ) {
26+ this . input . addEventListener ( name , listener ) ;
27+ }
28+ }
29+
30+ onKeyDown ( e : KeyboardEvent ) {
31+ const isApple = navigator . platform . startsWith ( "Mac" ) || navigator . platform === "iPhone" ;
32+ const keyParts = [
33+ e . shiftKey ? 'Shift' : null ,
34+ isApple && e . metaKey ? 'Mod' : null ,
35+ ! isApple && e . ctrlKey ? 'Mod' : null ,
36+ e . key ,
37+ ] ;
38+
39+ const keyString = keyParts . filter ( Boolean ) . join ( '-' ) ;
40+ if ( this . shortcuts [ keyString ] ) {
41+ e . preventDefault ( ) ;
42+ this . shortcuts [ keyString ] ( ) ;
43+ }
44+ }
45+
46+ appendText ( text : string ) : void {
47+ this . input . value += `\n${ text } ` ;
48+ }
49+
50+ coordsToSelection ( x : number , y : number ) : MarkdownEditorInputSelection {
51+ // TODO
52+ return this . getSelection ( ) ;
53+ }
54+
55+ focus ( ) : void {
56+ this . input . focus ( ) ;
57+ }
58+
59+ getLineRangeFromPosition ( position : number ) : MarkdownEditorInputSelection {
60+ const lines = this . getText ( ) . split ( '\n' ) ;
61+ let lineStart = 0 ;
62+ for ( let i = 0 ; i < lines . length ; i ++ ) {
63+ const line = lines [ i ] ;
64+ const newEnd = lineStart + line . length + 1 ;
65+ if ( position < newEnd ) {
66+ return { from : lineStart , to : newEnd } ;
67+ }
68+ lineStart = newEnd ;
69+ }
70+
71+ return { from : 0 , to : 0 } ;
72+ }
73+
74+ getLineText ( lineIndex : number ) : string {
75+ const text = this . getText ( ) ;
76+ const lines = text . split ( "\n" ) ;
77+ return lines [ lineIndex ] || '' ;
78+ }
79+
80+ getSelection ( ) : MarkdownEditorInputSelection {
81+ return { from : this . input . selectionStart , to : this . input . selectionEnd } ;
82+ }
83+
84+ getSelectionText ( selection ?: MarkdownEditorInputSelection ) : string {
85+ const text = this . getText ( ) ;
86+ const range = selection || this . getSelection ( ) ;
87+ return text . slice ( range . from , range . to ) ;
88+ }
89+
90+ getText ( ) : string {
91+ return this . input . value ;
92+ }
93+
94+ getTextAboveView ( ) : string {
95+ const scrollTop = this . input . scrollTop ;
96+ const computedStyles = window . getComputedStyle ( this . input ) ;
97+ const lines = this . getText ( ) . split ( '\n' ) ;
98+ const paddingTop = Number ( computedStyles . paddingTop . replace ( 'px' , '' ) ) ;
99+ const paddingBottom = Number ( computedStyles . paddingBottom . replace ( 'px' , '' ) ) ;
100+
101+ const avgLineHeight = ( this . input . scrollHeight - paddingBottom - paddingTop ) / lines . length ;
102+ const roughLinePos = Math . max ( Math . floor ( ( scrollTop - paddingTop ) / avgLineHeight ) , 0 ) ;
103+ const linesAbove = this . getText ( ) . split ( '\n' ) . slice ( 0 , roughLinePos ) ;
104+ return linesAbove . join ( '\n' ) ;
105+ }
106+
107+ searchForLineContaining ( text : string ) : MarkdownEditorInputSelection | null {
108+ const textPosition = this . getText ( ) . indexOf ( text ) ;
109+ if ( textPosition > - 1 ) {
110+ return this . getLineRangeFromPosition ( textPosition ) ;
111+ }
112+
113+ return null ;
114+ }
115+
116+ setSelection ( selection : MarkdownEditorInputSelection , scrollIntoView : boolean ) : void {
117+ this . input . selectionStart = selection . from ;
118+ this . input . selectionEnd = selection . to ;
119+ }
120+
121+ setText ( text : string , selection ?: MarkdownEditorInputSelection ) : void {
122+ this . input . value = text ;
123+ if ( selection ) {
124+ this . setSelection ( selection , false ) ;
125+ }
126+ }
127+
128+ spliceText ( from : number , to : number , newText : string , selection : Partial < MarkdownEditorInputSelection > | null ) : void {
129+ const text = this . getText ( ) ;
130+ const updatedText = text . slice ( 0 , from ) + newText + text . slice ( to ) ;
131+ this . setText ( updatedText ) ;
132+ if ( selection && selection . from ) {
133+ const newSelection = { from : selection . from , to : selection . to || selection . from } ;
134+ this . setSelection ( newSelection , false ) ;
135+ }
136+ }
137+ }
0 commit comments