@@ -1717,10 +1717,17 @@ function parseCountedToken(raw) {
17171717 return { value : token , count : 1 } ;
17181718}
17191719
1720+ function normalizeImportedPatternText ( text ) {
1721+ return String ( text || "" )
1722+ . replace ( / [ , 、 ] / g, "," )
1723+ . replace ( / [ ( ] / g, "(" )
1724+ . replace ( / [ ) ] / g, ")" ) ;
1725+ }
1726+
17201727function parseImportedItems ( text ) {
17211728 const items = [ ] ;
17221729 const unresolved = [ ] ;
1723- splitPatternTokens ( text ) . forEach ( ( token ) => {
1730+ splitPatternTokens ( normalizeImportedPatternText ( text ) ) . forEach ( ( token ) => {
17241731 const { value, count } = parseCountedToken ( token ) ;
17251732 const groupMatch = value . match ( / ^ [ \( ( ] ( .+ ) [ \) ) ] $ / ) ;
17261733 if ( groupMatch ) {
@@ -1729,7 +1736,14 @@ function parseImportedItems(text) {
17291736 const parsed = parseCountedToken ( innerToken ) ;
17301737 const stitch = findStitch ( parsed . value ) ;
17311738 if ( ! stitch ) {
1732- unresolved . push ( innerToken ) ;
1739+ const compactItems = splitCompactStitches ( parsed . value ) ;
1740+ if ( compactItems . length ) {
1741+ compactItems . forEach ( ( item ) => {
1742+ groupItems . push ( { ...item , count : item . count * parsed . count } ) ;
1743+ } ) ;
1744+ } else {
1745+ unresolved . push ( innerToken ) ;
1746+ }
17331747 return ;
17341748 }
17351749 groupItems . push ( { stitchId : stitch . id , count : parsed . count } ) ;
@@ -1739,7 +1753,12 @@ function parseImportedItems(text) {
17391753 }
17401754 const stitch = findStitch ( value ) ;
17411755 if ( ! stitch ) {
1742- unresolved . push ( token ) ;
1756+ const compactItems = splitCompactStitches ( value ) ;
1757+ if ( compactItems . length ) {
1758+ items . push ( { type : "group" , groupName : value , count, items : compactItems } ) ;
1759+ } else {
1760+ unresolved . push ( token ) ;
1761+ }
17431762 return ;
17441763 }
17451764 items . push ( { stitchId : stitch . id , count } ) ;
@@ -1770,8 +1789,12 @@ function parseTextPattern(text) {
17701789 if ( / ^ - { 3 , } $ / . test ( raw ) ) return ;
17711790 const match = raw . match ( / ^ (?: R | 第 ) ? \s * ( \d + ) (?: \s * [ - ~ ~ - ] \s * ( \d + ) ) ? (?: \s * [ 圈 段 ] ) ? (?: : | : | \s + ) \s * ( .+ ) $ / i) ;
17721791 if ( ! match ) {
1773- if ( / ^ [ \u4e00 - \u9fa5 A - Z a - z ] [ \u4e00 - \u9fa5 A - Z a - z 0 - 9 \s _ - ] * $ / . test ( raw ) ) {
1774- currentPart = { id : crypto . randomUUID ( ) , name : raw , notes : "" , segments : [ ] } ;
1792+ const [ partNameText , ...partNoteParts ] = raw . split ( "//" ) ;
1793+ const partName = partNameText . trim ( ) ;
1794+ const partNote = partNoteParts . join ( "//" ) . trim ( ) ;
1795+ const titleMatch = partName . match ( / ^ [ \u4e00 - \u9fa5 A - Z a - z ] [ \u4e00 - \u9fa5 A - Z a - z 0 - 9 \s _ * \- × x X ] * $ / ) ;
1796+ if ( titleMatch ) {
1797+ currentPart = { id : crypto . randomUUID ( ) , name : partName , notes : partNote , segments : [ ] } ;
17751798 parts . push ( currentPart ) ;
17761799 } else {
17771800 unparsed . push ( `第 ${ index + 1 } 行:${ raw } ` ) ;
@@ -1780,15 +1803,17 @@ function parseTextPattern(text) {
17801803 }
17811804 const start = Number ( match [ 1 ] ) ;
17821805 const end = Math . max ( start , Number ( match [ 2 ] || start ) ) ;
1783- const parsed = parseImportedItems ( match [ 3 ] ) ;
1806+ const [ stitchText , ...noteParts ] = match [ 3 ] . split ( "//" ) ;
1807+ const note = noteParts . join ( "//" ) . trim ( ) ;
1808+ const parsed = parseImportedItems ( stitchText ) ;
17841809 if ( ! parsed . items . length ) {
17851810 unparsed . push ( `第 ${ index + 1 } 行:${ raw } ` ) ;
17861811 return ;
17871812 }
17881813 parsed . unresolved . forEach ( ( item ) => unparsed . push ( `第 ${ index + 1 } 行無法辨識:${ item } ` ) ) ;
17891814 const part = ensurePart ( ) ;
17901815 for ( let round = start ; round <= end ; round += 1 ) {
1791- part . segments . push ( { id : crypto . randomUUID ( ) , repeat : 1 , note : "" , items : structuredClone ( parsed . items ) } ) ;
1816+ part . segments . push ( { id : crypto . randomUUID ( ) , repeat : 1 , note, items : structuredClone ( parsed . items ) } ) ;
17921817 }
17931818 } ) ;
17941819 const validParts = parts . filter ( ( part ) => part . segments . length ) ;
@@ -1801,6 +1826,24 @@ function findStitch(value) {
18011826 return state . stitches . find ( ( stitch ) => [ stitch . id , stitch . zh , stitch . letter ] . some ( ( item ) => String ( item ) . toLowerCase ( ) === normalized ) ) ;
18021827}
18031828
1829+ function splitCompactStitches ( value ) {
1830+ const text = String ( value || "" ) . trim ( ) ;
1831+ if ( ! / ^ [ A - Z a - z ] + $ / . test ( text ) ) return [ ] ;
1832+ const letters = state . stitches
1833+ . map ( ( stitch ) => ( { id : stitch . id , letter : String ( stitch . letter || "" ) . trim ( ) } ) )
1834+ . filter ( ( item ) => item . letter && / ^ [ A - Z a - z ] + $ / . test ( item . letter ) )
1835+ . sort ( ( a , b ) => b . letter . length - a . letter . length ) ;
1836+ const result = [ ] ;
1837+ let index = 0 ;
1838+ while ( index < text . length ) {
1839+ const match = letters . find ( ( item ) => text . slice ( index , index + item . letter . length ) . toLowerCase ( ) === item . letter . toLowerCase ( ) ) ;
1840+ if ( ! match ) return [ ] ;
1841+ result . push ( { stitchId : match . id , count : 1 } ) ;
1842+ index += match . letter . length ;
1843+ }
1844+ return result . length > 1 ? result : [ ] ;
1845+ }
1846+
18041847function expandedRows ( pattern ) {
18051848 const rows = [ ] ;
18061849 pattern . parts . forEach ( ( part ) => {
@@ -5804,6 +5847,24 @@ document.querySelectorAll(".modal").forEach((modal) => {
58045847 } ) ;
58055848} ) ;
58065849
5850+ function selectNumericInput ( input ) {
5851+ if ( ! ( input instanceof HTMLInputElement ) ) return ;
5852+ if ( input . type !== "number" || input . disabled || input . readOnly ) return ;
5853+ requestAnimationFrame ( ( ) => {
5854+ try {
5855+ input . select ( ) ;
5856+ } catch { }
5857+ } ) ;
5858+ }
5859+
5860+ document . addEventListener ( "focusin" , ( event ) => {
5861+ selectNumericInput ( event . target ) ;
5862+ } ) ;
5863+
5864+ document . addEventListener ( "pointerup" , ( event ) => {
5865+ if ( event . target === document . activeElement ) selectNumericInput ( event . target ) ;
5866+ } ) ;
5867+
58075868registerServiceWorker ( ) ;
58085869if ( "scrollRestoration" in history ) history . scrollRestoration = "manual" ;
58095870requestAnimationFrame ( ( ) => {
0 commit comments