@@ -14,13 +14,36 @@ type YancyUpdateMessage = YancyEditorMessage & {
1414 content : string ;
1515 } ;
1616} ;
17+ type YancyStyleMessage = YancyEditorMessage & {
18+ name : "style" ;
19+ tag ?: string ;
20+ class ?: string ;
21+ style ?: string ;
22+ } ;
1723
18- function handleBlockInput ( e : InputEvent ) {
24+ function handleBlockClick ( e : PointerEvent ) {
1925 if ( ! ( e . target instanceof HTMLElement ) ) {
2026 return ;
2127 }
22- console . debug ( "block input event" , e ) ;
23- const blockEl = e . target ;
28+ console . debug ( "block click event" , e ) ;
29+ const msg = {
30+ name : "focus" ,
31+ stack : [ ] ,
32+ } ;
33+ let el = e . target ;
34+ while ( el ) {
35+ msg . stack . push ( {
36+ tag : el . tagName . toLowerCase ( ) ,
37+ class : el . className ,
38+ style : el . style . cssText ,
39+ } ) ;
40+ el = el . parentElement ;
41+ }
42+ Yancy . editorPort . postMessage ( msg ) ;
43+ e . stopPropagation ( ) ;
44+ }
45+
46+ function sendInputMessage ( blockEl : HTMLElement ) {
2447 const blockData = {
2548 block_id : blockEl . getAttribute ( "block_id" ) ,
2649 name : blockEl . getAttribute ( "name" ) ,
@@ -33,13 +56,36 @@ function handleBlockInput(e: InputEvent) {
3356 } ) ;
3457}
3558
59+ function handleBlockInput ( e : InputEvent ) {
60+ if ( ! ( e . target instanceof HTMLElement ) ) {
61+ return ;
62+ }
63+ console . debug ( "block input event" , e ) ;
64+ sendInputMessage ( e . target ) ;
65+ }
66+
67+ function handleBodyClick ( e : PointerEvent ) {
68+ // We can click on the window without technically removing
69+ // focus from one of the contenteditable elements, so make
70+ // sure we don't still have a good focus...
71+ if ( document . activeElement . closest ( "[contenteditable]" ) ) {
72+ return ;
73+ }
74+ const msg = {
75+ name : "blur" ,
76+ } ;
77+ Yancy . editorPort . postMessage ( msg ) ;
78+ }
79+
3680function handleEvent ( e : MessageEvent < YancyEditorMessage > ) {
3781 console . debug ( "got message from editor" , e ) ;
3882 if ( e . data . name === "enable" ) {
83+ window . addEventListener ( "click" , handleBodyClick ) ;
3984 for ( const block of Array . from ( document . querySelectorAll ( "y-block" ) ) ) {
4085 if ( block instanceof HTMLElement ) {
4186 block . contentEditable = "true" ;
4287 block . addEventListener ( "input" , handleBlockInput ) ;
88+ block . addEventListener ( "click" , handleBlockClick ) ;
4389 }
4490 }
4591 } else if ( e . data . name === "update" ) {
@@ -51,7 +97,71 @@ function handleEvent(e: MessageEvent<YancyEditorMessage>) {
5197 `y-block[name=${ updateEvent . block . name } ]` ,
5298 ) ;
5399 block . setAttribute ( "block_id" , "" + updateEvent . block . block_id ) ;
100+ block . innerHTML = updateEvent . block . content ;
101+ }
102+ } else if ( e . data . name === "style" ) {
103+ const styleEvent = e . data as YancyStyleMessage ;
104+ const sel = getSelection ( ) ;
105+ console . log ( "selection at start" , sel ) ;
106+ if ( ! sel ) {
107+ console . error ( "Cannot update style: No selection" ) ;
108+ return ;
109+ }
110+ console . debug ( "changing style" , styleEvent ) ;
111+ // If we're in a bare text node (parent node is not a text style),
112+ // pretend we're in a <p> node that surrounds all the text
113+ // we can reach from where we are without crossing another
114+ // element node.
115+ const anchorNode = sel . anchorNode ;
116+ const inText = ! ( anchorNode instanceof HTMLElement ) ;
117+ // Just gotta change the tag name of our parent element?
118+ const oldParent = inText ? anchorNode . parentElement : anchorNode ;
119+ const blockEl = oldParent . closest ( "y-block" ) as HTMLElement ;
120+ const textStyles = [ "p" , "h1" , "h2" , "h3" , "h4" , "h5" , "h6" ] ;
121+ if ( ! textStyles . includes ( oldParent . tagName . toLowerCase ( ) ) ) {
122+ console . log ( "adding wrapper around current text..." ) ;
123+ const textNodes = [ anchorNode ] ;
124+ let testNode = anchorNode . previousSibling ;
125+ while ( testNode && testNode . nodeType !== Node . ELEMENT_NODE ) {
126+ textNodes . unshift ( testNode ) ;
127+ testNode = testNode . previousSibling ;
128+ }
129+ testNode = anchorNode . nextSibling ;
130+ while ( testNode && testNode . nodeType !== Node . ELEMENT_NODE ) {
131+ textNodes . push ( testNode ) ;
132+ testNode = testNode . nextSibling ;
133+ }
134+ const parentNode = anchorNode . parentNode ;
135+ const newParent = document . createElement ( styleEvent . tag ) ;
136+ console . log (
137+ "Wrapping text nodes" ,
138+ textNodes ,
139+ "parent" ,
140+ parentNode ,
141+ "nextSibling" ,
142+ testNode ,
143+ ) ;
144+ newParent . append ( ...textNodes ) ;
145+ parentNode . insertBefore ( newParent , testNode ) ;
146+
147+ // Need to fix the selection now that we've changed everything
148+ sel . setPosition (
149+ inText ? newParent . childNodes [ 0 ] : newParent ,
150+ sel . anchorOffset ,
151+ ) ;
152+ } else {
153+ const newParent = document . createElement ( styleEvent . tag ) ;
154+ newParent . append ( ...Array . from ( oldParent . childNodes ) ) ;
155+ oldParent . replaceWith ( newParent ) ;
156+
157+ // Need to fix the selection now that we've changed everything
158+ sel . setPosition (
159+ inText ? newParent . childNodes [ 0 ] : newParent ,
160+ sel . anchorOffset ,
161+ ) ;
54162 }
163+ console . log ( "selection at end" , sel ) ;
164+ sendInputMessage ( blockEl ) ;
55165 }
56166}
57167
0 commit comments