11import { MarkerContent } from "@biblionexus-foundation/scripture-utilities" ;
22import { SerializedVerseRef } from "@sillsdev/scripture" ;
33import {
4+ $createTextNode ,
45 $getSelection ,
56 $isElementNode ,
67 $isRangeSelection ,
@@ -19,6 +20,7 @@ import { ParaNode } from "shared/nodes/scripture/usj/ParaNode";
1920import { MarkerAction } from "shared/utils/get-marker-action.model" ;
2021import { Marker } from "shared/utils/usfm/usfmTypes" ;
2122import { createLexicalUsjNode } from "shared/utils/usj/contentToLexicalNode" ;
23+ import { $isSomeVerseNode } from "shared-react/nodes/scripture/usj/node-react.utils" ;
2224import { ViewOptions } from "./view-options.utils" ;
2325import usjEditorAdaptor from "./usj-editor.adaptor" ;
2426
@@ -33,26 +35,24 @@ const markerActions: {
3335} = {
3436 c : {
3537 action : ( currentEditor ) => {
36- const { book , chapterNum } = currentEditor . reference ;
38+ const { chapterNum } = currentEditor . reference ;
3739 const nextChapter = chapterNum + 1 ;
3840 const content : MarkerContent = {
3941 type : "chapter" ,
4042 marker : "c" ,
4143 number : `${ nextChapter } ` ,
42- sid : `${ book } ${ nextChapter } ` ,
4344 } ;
4445 return [ content ] ;
4546 } ,
4647 } ,
4748 v : {
4849 action : ( currentEditor ) => {
49- const { book , chapterNum , verseNum, verse } = currentEditor . reference ;
50+ const { verseNum, verse } = currentEditor . reference ;
5051 const nextVerse = getNextVerse ( verseNum , verse ) ;
5152 const content : MarkerContent = {
5253 type : "verse" ,
5354 marker : "v" ,
5455 number : `${ nextVerse } ` ,
55- sid : `${ book } ${ chapterNum } :${ nextVerse } ` ,
5656 } ;
5757 return [ content ] ;
5858 } ,
@@ -110,7 +110,7 @@ export function getUsjMarkerAction(
110110 if ( ! content ) return ;
111111
112112 const serializedLexicalNode = createLexicalUsjNode ( content , usjEditorAdaptor , viewOptions ) ;
113- const usjNode = $createNodeFromSerializedNode ( serializedLexicalNode ) ;
113+ const nodeToInsert = $createNodeFromSerializedNode ( serializedLexicalNode ) ;
114114
115115 const selection = $getSelection ( ) ;
116116 if ( $isRangeSelection ( selection ) ) {
@@ -119,22 +119,23 @@ export function getUsjMarkerAction(
119119 $wrapTextSelectionInInlineNode ( selection , ( ) =>
120120 $createNodeFromSerializedNode ( serializedLexicalNode ) ,
121121 ) ;
122- } else if ( $isElementNode ( usjNode ) && ! usjNode . isInline ( ) ) {
122+ } else if ( $isElementNode ( nodeToInsert ) && ! nodeToInsert . isInline ( ) ) {
123123 // If the selection is empty, insert a new paragraph and replace it with the USJ node
124124 const paragraph = selection . insertParagraph ( ) ;
125125 if ( paragraph ) {
126126 // Transfer the content of the paragraph to the USJ node
127127 const paragraphContent = paragraph . getChildren ( ) ;
128- usjNode . append ( ...paragraphContent ) ;
129- paragraph . replace ( usjNode ) ;
130- usjNode . selectStart ( ) ;
128+ nodeToInsert . append ( ...paragraphContent ) ;
129+ paragraph . replace ( nodeToInsert ) ;
130+ nodeToInsert . selectStart ( ) ;
131131 }
132132 } else {
133- selection . insertNodes ( [ usjNode ] ) ;
133+ const nodes = $addVerseLeadingSpaceIfNeeded ( selection , nodeToInsert ) ;
134+ selection . insertNodes ( nodes ) ;
134135 }
135136 } else {
136- // Insert the USJ node directly
137- selection ?. insertNodes ( [ usjNode ] ) ;
137+ // Insert the node directly
138+ selection ?. insertNodes ( [ nodeToInsert ] ) ;
138139 }
139140 } ) ;
140141 } ;
@@ -193,7 +194,7 @@ function $wrapTextSelectionInInlineNode(
193194 }
194195
195196 // Get the target node to wrap
196- const targetNode = getTargetNode (
197+ const targetNode = $ getTargetNode(
197198 node ,
198199 index === 0 ,
199200 index === nodes . length - 1 ,
@@ -213,7 +214,7 @@ function $wrapTextSelectionInInlineNode(
213214 }
214215
215216 // Wrap the target node
216- wrapNode ( targetNode , currentWrapper ) ;
217+ $ wrapNode( targetNode , currentWrapper ) ;
217218 } ) ;
218219
219220 // Update selection
@@ -235,7 +236,7 @@ function getSelectionOffsets(selection: RangeSelection): [number, number] {
235236 return selection . isBackward ( ) ? [ focusOffset , anchorOffset ] : [ anchorOffset , focusOffset ] ;
236237}
237238
238- function getTargetNode (
239+ function $ getTargetNode(
239240 node : LexicalNode ,
240241 isFirst : boolean ,
241242 isLast : boolean ,
@@ -282,7 +283,7 @@ function handleTextNode(
282283 return splitNodes . length === 3 || isFirst || end === textLength ? splitNodes [ 1 ] : splitNodes [ 0 ] ;
283284}
284285
285- function wrapNode ( node : LexicalNode , wrapper : LexicalNode ) : void {
286+ function $ wrapNode( node : LexicalNode , wrapper : LexicalNode ) : void {
286287 if ( $isTextNode ( wrapper ) ) {
287288 wrapper . setTextContent ( node . getTextContent ( ) ) ;
288289 node . remove ( ) ;
@@ -293,3 +294,33 @@ function wrapNode(node: LexicalNode, wrapper: LexicalNode): void {
293294}
294295
295296// #endregion
297+
298+ /**
299+ * Adds a leading space before a verse node if needed.
300+ *
301+ * This function checks if the given selection is collapsed and if the node to be inserted is a
302+ * verse node or an immutable verse node. If both conditions are met, it further checks if the
303+ * anchor node of the selection is a text node and if there is no leading space before the insertion
304+ * point. If there is no leading space, it prepends a space to the node to be inserted.
305+ *
306+ * @param selection - The current selection range in the editor.
307+ * @param nodeToInsert - The node that is to be inserted into the editor.
308+ * @returns An array containing the nodes to be inserted, potentially with a leading space node.
309+ */
310+ function $addVerseLeadingSpaceIfNeeded (
311+ selection : RangeSelection ,
312+ nodeToInsert : LexicalNode ,
313+ ) : LexicalNode [ ] {
314+ if ( ! selection . isCollapsed ( ) ) return [ nodeToInsert ] ;
315+ if ( ! $isSomeVerseNode ( nodeToInsert ) ) return [ nodeToInsert ] ;
316+
317+ const anchorNode = selection . anchor . getNode ( ) ;
318+ if ( ! $isTextNode ( anchorNode ) ) return [ nodeToInsert ] ;
319+
320+ const offset = selection . anchor . offset ;
321+ const textContent = anchorNode . getTextContent ( ) ;
322+ const hasLeadingSpace = textContent [ offset - 1 ] === " " ;
323+ if ( hasLeadingSpace ) return [ nodeToInsert ] ;
324+
325+ return [ $createTextNode ( " " ) , nodeToInsert ] ;
326+ }
0 commit comments