@@ -21,15 +21,16 @@ function processCloseSpanMarkers(elements: Element[]): Element[] {
2121 if ( ! elem ) continue ;
2222
2323 // Check for closeSpan marker
24- // _closeSpan is on data directly, not data.attributes
2524 if (
2625 elem . element === "container" &&
2726 elem . data &&
2827 typeof elem . data === "object" &&
2928 "type" in elem . data &&
3029 elem . data . type === "span" &&
31- "_closeSpan" in elem . data &&
32- ( elem . data as any ) . _closeSpan === true
30+ "attributes" in elem . data &&
31+ typeof elem . data . attributes === "object" &&
32+ elem . data . attributes &&
33+ "_closeSpan" in elem . data . attributes
3334 ) {
3435 // Wrap all preceding content in a span
3536 if ( result . length > 0 ) {
@@ -79,50 +80,6 @@ export const paragraphRule: BlockRule = {
7980 // Process closeSpan markers (for split spans)
8081 let elements = processCloseSpanMarkers ( result . elements ) ;
8182
82- // Split paragraph at aligned images (they become block-level elements)
83- // This also removes float center images (invalid in Wikidot)
84- const splitResult = splitAtAlignedImages ( elements ) ;
85- const hasAlignedImages = splitResult . some ( ( part ) => part . type === "image" ) ;
86-
87- if ( splitResult . length > 1 || hasAlignedImages ) {
88- // Return multiple elements: paragraphs and standalone images
89- const outputElements : Element [ ] = [ ] ;
90- for ( const part of splitResult ) {
91- if ( part . type === "image" ) {
92- outputElements . push ( part . element ) ;
93- } else if ( part . elements . length > 0 ) {
94- // Clean up paragraph elements
95- const cleaned = cleanParagraphElements ( part . elements ) ;
96- if ( cleaned . length > 0 ) {
97- outputElements . push ( {
98- element : "container" ,
99- data : {
100- type : "paragraph" ,
101- attributes : { } ,
102- elements : cleaned ,
103- } ,
104- } ) ;
105- }
106- }
107- }
108- if ( outputElements . length === 0 ) {
109- return { success : false } ;
110- }
111- return {
112- success : true ,
113- elements : outputElements ,
114- consumed : result . consumed ,
115- } ;
116- }
117-
118- // Rebuild elements from splitResult (may have float center removed)
119- elements = [ ] ;
120- for ( const part of splitResult ) {
121- if ( part . type === "text" ) {
122- elements . push ( ...part . elements ) ;
123- }
124- }
125-
12683 // Remove trailing line-breaks (they shouldn't appear at end of paragraph)
12784 // Exception: line-breaks flagged by preserveTrailingLineBreak context are kept
12885 while ( elements . length > 0 && elements [ elements . length - 1 ] ?. element === "line-break" ) {
@@ -198,95 +155,3 @@ function parseInlineContent(ctx: ParseContext): {
198155 // The parser will stop at double NEWLINE (paragraph break)
199156 return parseInlineUntil ( ctx , "PARAGRAPH_BREAK" as any ) ;
200157}
201-
202- type SplitPart = { type : "text" ; elements : Element [ ] } | { type : "image" ; element : Element } ;
203-
204- /**
205- * Split elements at aligned images
206- * Aligned images become block-level elements, splitting the paragraph
207- * Float center images are removed entirely (invalid in Wikidot)
208- */
209- function splitAtAlignedImages ( elements : Element [ ] ) : SplitPart [ ] {
210- const parts : SplitPart [ ] = [ ] ;
211- let currentText : Element [ ] = [ ] ;
212-
213- for ( let i = 0 ; i < elements . length ; i ++ ) {
214- const elem = elements [ i ] ;
215- if ( ! elem ) continue ;
216-
217- if ( isAlignedImage ( elem ) ) {
218- const imageData = ( elem as any ) . data ;
219- // Float center is invalid - skip the image AND preceding text on same line
220- if ( imageData ?. alignment ?. float && imageData . alignment . align === "center" ) {
221- // Remove text preceding the image on the same line (back to last line-break)
222- while ( currentText . length > 0 ) {
223- const last = currentText [ currentText . length - 1 ] ;
224- if ( last ?. element === "line-break" ) {
225- break ;
226- }
227- currentText . pop ( ) ;
228- }
229- // Also skip line-break after the image if present
230- if ( elements [ i + 1 ] ?. element === "line-break" ) {
231- i ++ ;
232- }
233- continue ;
234- }
235-
236- // Save current text as a part
237- if ( currentText . length > 0 ) {
238- parts . push ( { type : "text" , elements : [ ...currentText ] } ) ;
239- currentText = [ ] ;
240- }
241- // Add image as standalone element
242- parts . push ( { type : "image" , element : elem } ) ;
243- } else {
244- currentText . push ( elem ) ;
245- }
246- }
247-
248- // Add remaining text
249- if ( currentText . length > 0 ) {
250- parts . push ( { type : "text" , elements : currentText } ) ;
251- }
252-
253- return parts ;
254- }
255-
256- /**
257- * Check if element is an aligned image (has alignment property)
258- */
259- function isAlignedImage ( elem : Element ) : boolean {
260- if ( elem . element !== "image" ) return false ;
261- const data = ( elem as any ) . data ;
262- return data ?. alignment != null ;
263- }
264-
265- /**
266- * Clean up paragraph elements (remove trailing/leading line-breaks)
267- */
268- function cleanParagraphElements ( elements : Element [ ] ) : Element [ ] {
269- let result = [ ...elements ] ;
270-
271- // Remove trailing line-breaks
272- while ( result . length > 0 && result [ result . length - 1 ] ?. element === "line-break" ) {
273- result . pop ( ) ;
274- }
275-
276- // Remove trailing whitespace
277- while ( result . length > 0 ) {
278- const last = result [ result . length - 1 ] ;
279- if ( last ?. element === "text" && typeof last . data === "string" && last . data . trim ( ) === "" ) {
280- result . pop ( ) ;
281- } else {
282- break ;
283- }
284- }
285-
286- // Remove leading line-breaks
287- while ( result . length > 0 && result [ 0 ] ?. element === "line-break" ) {
288- result . shift ( ) ;
289- }
290-
291- return result ;
292- }
0 commit comments