@@ -581,24 +581,29 @@ <h2 class="result-heading">Macronized</h2>
581581 // Orthography options of the last run — wordlist forms use classical j/v and
582582 // must be converted back to i/u unless the user enabled those conversions
583583 let lastOrtho = { utov : false , itoj : false } ;
584- function applyOrtho ( c ) {
585- if ( ! lastOrtho . itoj ) c = c . replace ( / j / g, 'i' ) . replace ( / J / g, 'I' ) ;
586- if ( ! lastOrtho . utov ) c = c . replace ( / v / g, 'u' ) . replace ( / V / g, 'U' ) ;
584+ // A displayed word mirrors the orthography the USER typed it with: "divisa"
585+ // stays dīvīsa, "diuisa" stays dīuīsa. The i→j / u→v checkboxes still force the
586+ // classical forms regardless of input. Without the input mirror, a word typed
587+ // with v shows its wordlist v form first, the first click converts it to u, and
588+ // the original spelling is unreachable (the divisa/diviso bug).
589+ function applyOrtho ( c , inputText ) {
590+ if ( ! ( lastOrtho . itoj || ( inputText && / [ j J ] / . test ( inputText ) ) ) ) c = c . replace ( / j / g, 'i' ) . replace ( / J / g, 'I' ) ;
591+ if ( ! ( lastOrtho . utov || ( inputText && / [ v V ] / . test ( inputText ) ) ) ) c = c . replace ( / v / g, 'u' ) . replace ( / V / g, 'U' ) ;
587592 return c ;
588593 }
589594
590595 // wordlist form → text form used for cycling: a_ → ā, breve/ambiguity markers stripped
591- function candidateToDisplay ( c ) {
592- return applyOrtho ( underscoreToUnicode ( c ) . replace ( / [ \^ + _ ] / g, '' ) ) ;
596+ function candidateToDisplay ( c , inputText ) {
597+ return applyOrtho ( underscoreToUnicode ( c ) . replace ( / [ \^ + _ ] / g, '' ) , inputText ) ;
593598 }
594599
595600 // wordlist form → popup form: also render breves (a^ → ă) so candidates that
596601 // differ only in vowel shortness stay distinguishable (me^mo^rem vs me^morem)
597602 const BREVE_MAP = { a :'ă' , e :'ĕ' , i :'ĭ' , o :'ŏ' , u :'ŭ' , A :'Ă' , E :'Ĕ' , I :'Ĭ' , O :'Ŏ' , U :'Ŭ' } ;
598- function candidateToPopup ( c ) {
603+ function candidateToPopup ( c , inputText ) {
599604 return applyOrtho ( underscoreToUnicode ( c )
600605 . replace ( / ( [ a - z A - Z ā ē ī ō ū ȳ Ā Ē Ī Ō Ū Ȳ ] ) \^ / g, ( m , v ) => BREVE_MAP [ v ] || ( v + '̆' ) )
601- . replace ( / [ + _ ] / g, '' ) ) ;
606+ . replace ( / [ + _ ] / g, '' ) , inputText ) ;
602607 }
603608
604609 await window . __wasmReady ;
@@ -657,8 +662,16 @@ <h2 class="result-heading">Macronized</h2>
657662
658663 // Strip macrons/breves from input — the pipeline expects unmarked text
659664 // (macronized words would otherwise be torn apart and sent to Morpheus as unknowns)
660- const text = document . getElementById ( 'text_to_macronize' ) . value
665+ let text = document . getElementById ( 'text_to_macronize' ) . value
661666 . normalize ( 'NFD' ) . replace ( / [ ̄ ̆ ] / g, '' ) . normalize ( 'NFC' ) ;
667+ // Latin Library / textbook sources tag lines with trailing verse numbers
668+ // ("urbem, 5", "libellum 1.1"). A trailing number is reference noise, not text —
669+ // and by changing the last word's "following segment" it makes the verse
670+ // impossible to scan (the grey box). Strip them, same as the miner's corpus prep.
671+ text = text . split ( '\n' ) . map ( line =>
672+ line . replace ( / ^ \s * \d + (?: [ . , ] \d + ) * \s * $ / , '' )
673+ . replace ( / \s * \d + (?: [ . , ] \d + ) * \s * $ / , '' )
674+ ) . join ( '\n' ) ;
662675 if ( ! text . trim ( ) ) { showError ( 'Please enter some text' ) ; return ; }
663676
664677 document . getElementById ( 'loading' ) . style . display = 'block' ;
@@ -800,7 +813,7 @@ <h2 class="result-heading">Macronized</h2>
800813 // Build rich popup HTML for a token (table layout)
801814 function buildPopupHtml ( token , displayText , encliticText ) {
802815 // popup shows breved forms so quantity-only differences stay visible
803- const candidates = [ ...new Set ( ( token . accented || [ ] ) . map ( candidateToPopup ) ) ] ;
816+ const candidates = [ ...new Set ( ( token . accented || [ ] ) . map ( c => candidateToPopup ( c , token . text ) ) ) ] ;
804817
805818 const flags = [ ] ;
806819 if ( token . isAmbiguous ) flags . push ( 'Ambiguous' ) ;
@@ -817,7 +830,7 @@ <h2 class="result-heading">Macronized</h2>
817830 const seen = new Set ( ) ;
818831 const pairs = [ ] ;
819832 ( token . accented || [ ] ) . forEach ( ( c , i ) => {
820- const shown = matchCase ( token . text , candidateToPopup ( c ) ) + ( encliticText || '' ) ;
833+ const shown = matchCase ( token . text , candidateToPopup ( c , token . text ) ) + ( encliticText || '' ) ;
821834 if ( seen . has ( shown ) ) return ;
822835 seen . add ( shown ) ;
823836 // each reading carries the lemma + tag of the wordlist row it came from,
@@ -826,7 +839,7 @@ <h2 class="result-heading">Macronized</h2>
826839 : sources . find ( s => s . accented === c ) ;
827840 pairs . push ( {
828841 shown,
829- plain : matchCase ( token . text , candidateToDisplay ( c ) ) + ( encliticText || '' ) ,
842+ plain : matchCase ( token . text , candidateToDisplay ( c , token . text ) ) + ( encliticText || '' ) ,
830843 lemma : src && src . lemma ? src . lemma : null ,
831844 tag : src && src . tag ? src . tag : null
832845 } ) ;
@@ -1102,6 +1115,10 @@ <h2 class="result-heading">Macronized</h2>
11021115 }
11031116
11041117 const scannedFeet = result . scannedFeet || [ ] ;
1118+ // Prose never scans, so in prose mode scannedFeet is empty and every line
1119+ // would otherwise get the "no scansion" placeholder — noise. Only render the
1120+ // foot cell when a meter is selected.
1121+ const scanMode = document . getElementById ( 'scan' ) . value ;
11051122 const tbody = document . getElementById ( 'resultText' ) ;
11061123 tbody . innerHTML = '' ;
11071124
@@ -1112,7 +1129,13 @@ <h2 class="result-heading">Macronized</h2>
11121129 const td = document . createElement ( 'td' ) ;
11131130 td . className = 'macron-line' ;
11141131
1115- const tokenDisplay = t => ( ( t . macronizedText || t . text ) . replace ( / [ \^ + _ ] / g, '' ) ) || t . text ;
1132+ // Must mirror the cycle candidates (ortho + case) or indexOf(cur) misses and
1133+ // the first click jumps to a candidate the original spelling can never return to.
1134+ const tokenDisplay = t => {
1135+ const raw = ( t . macronizedText || t . text ) . replace ( / [ \^ + _ ] / g, '' ) ;
1136+ const ortho = matchCase ( t . text , applyOrtho ( raw , t . text ) ) ;
1137+ return ortho || raw ;
1138+ } ;
11161139
11171140 for ( let ti = 0 ; ti < lineTokens . length ; ti ++ ) {
11181141 const token = lineTokens [ ti ] ;
@@ -1138,7 +1161,7 @@ <h2 class="result-heading">Macronized</h2>
11381161 // make text selectable
11391162 //span.textContent = displayText;
11401163 const candidates = [ ...new Set (
1141- ( token . accented || [ ] ) . map ( c => matchCase ( token . text , candidateToDisplay ( c ) ) )
1164+ ( token . accented || [ ] ) . map ( c => matchCase ( token . text , candidateToDisplay ( c , token . text ) ) )
11421165 ) ] . map ( c => encliticText ? c + encliticText : c ) ;
11431166 span . __popupHtml = buildPopupHtml ( token , displayText , encliticText ) ;
11441167
@@ -1193,16 +1216,18 @@ <h2 class="result-heading">Macronized</h2>
11931216 td . appendChild ( span ) ;
11941217 }
11951218
1196- const foot = scannedFeet [ lineIdx ] ;
1197- const footSpan = document . createElement ( 'span' ) ;
1198- footSpan . className = 'verse-foot' ;
1199- if ( foot ) {
1200- footSpan . textContent = foot ;
1201- } else {
1202- footSpan . classList . add ( 'no-scan' ) ;
1203- footSpan . textContent = '—' ;
1219+ if ( scanMode !== 'prose' ) {
1220+ const foot = scannedFeet [ lineIdx ] ;
1221+ const footSpan = document . createElement ( 'span' ) ;
1222+ footSpan . className = 'verse-foot' ;
1223+ if ( foot ) {
1224+ footSpan . textContent = foot ;
1225+ } else {
1226+ footSpan . classList . add ( 'no-scan' ) ;
1227+ footSpan . textContent = '—' ;
1228+ }
1229+ td . appendChild ( footSpan ) ;
12041230 }
1205- td . appendChild ( footSpan ) ;
12061231 tr . appendChild ( td ) ;
12071232 tbody . appendChild ( tr ) ;
12081233 }
0 commit comments