@@ -5,39 +5,69 @@ export const shouldApplySmartQuotes = (config, target) => {
5
5
return ! ! smartQuotes && isValidQuotePairConfig ( quotes ) && isValidQuotePairConfig ( singleQuotes ) && target . isContentEditable
6
6
}
7
7
8
- // export const isQuote = (char) => /^[‘’‹›‚'«»"“”„]$/.test(char)
9
8
const isDoubleQuote = ( char ) => / ^ [ « » " “ ” „ ] $ / . test ( char )
10
9
const isSingleQuote = ( char ) => / ^ [ ‘ ’ ‹ › ‚ ' ] $ / . test ( char )
10
+ const isApostrophe = ( char ) => / ^ [ ’ ' ] $ / . test ( char )
11
11
12
- // Test: '>', ' ', no space & all kinds of dashes dash
13
- const isOpeningQuote = ( text , indexCharBefore ) => indexCharBefore < 0 || / \s | [ > \- – — ] / . test ( text [ indexCharBefore ] )
14
- const isClosingQuote = ( text , indexCharBefore ) => text [ indexCharBefore ] !== ' '
12
+ // TODO: Test with: '>', ' ', no space & all kinds of dashes dash
13
+ // edge case: applied tooltip quotes and then inserted single quote after space
14
+ const shouldBeOpeningQuote = ( text , indexCharBefore ) => indexCharBefore < 0 || / \s | [ > \- – — ] / . test ( text [ indexCharBefore ] )
15
+ const shouldBeClosingQuote = ( text , indexCharBefore ) => text [ indexCharBefore ] && ! / \s / . test ( text [ indexCharBefore ] )
15
16
16
- const applySmartQuote = ( textArr , index , target , quoteType ) => {
17
- if ( index >= 0 && index < textArr . length ) {
18
- textArr [ index ] = quoteType
19
- target . innerText = textArr . join ( '' )
17
+ const replaceQuote = ( range , index , quoteType ) => {
18
+ const startContainer = range . startContainer
19
+ const textNode = document . createTextNode ( `${ startContainer . nodeValue . substring ( 0 , index ) } ${ quoteType } ${ startContainer . nodeValue . substring ( index + 1 ) } ` )
20
+ range . startContainer . replaceWith ( textNode )
21
+ return textNode
22
+ }
23
+
24
+ const hasSingleOpeningQuote = ( textArr , offset , singleOpeningQuote ) => {
25
+ if ( offset <= 0 ) {
26
+ return false
20
27
}
28
+ for ( let i = offset - 1 ; i >= 0 ; i -- ) {
29
+ if ( isSingleQuote ( textArr [ i ] ) && ( ! isApostrophe ( singleOpeningQuote ) && ! isApostrophe ( textArr [ i ] ) ) ) {
30
+ return textArr [ i ] === singleOpeningQuote
31
+ }
32
+ }
33
+ return false
21
34
}
22
35
23
- export const applySmartQuotes = ( config , char , wholeText , offset , target , resetCursor ) => {
36
+ export const applySmartQuotes = ( range , config , char , target ) => {
24
37
const isCharSingleQuote = isSingleQuote ( char )
25
38
const isCharDoubleQuote = isDoubleQuote ( char )
26
39
27
40
if ( ! isCharDoubleQuote && ! isCharSingleQuote ) {
28
41
return
29
42
}
30
43
44
+ const offset = range . startOffset
45
+ const textArr = [ ...range . startContainer . textContent ]
31
46
const { quotes, singleQuotes} = config
32
- if ( isClosingQuote ( wholeText , offset - 2 ) ) {
47
+ let newTextNode
48
+
49
+ if ( shouldBeClosingQuote ( textArr , offset - 2 ) ) {
50
+ // Don't transform single-quote if there is no respective single-opening-quote
51
+ if ( isCharSingleQuote && ! hasSingleOpeningQuote ( textArr , offset , singleQuotes [ 0 ] ) ) {
52
+ return
53
+ }
54
+ // TODO: Fix ‹Didn’t› case -> only works with timeout
55
+ // if (isCharSingleQuote && hasTextAfter(target, offset)) {
56
+ // return
57
+ // }
33
58
const closingQuote = isCharSingleQuote ? singleQuotes [ 1 ] : quotes [ 1 ]
34
- applySmartQuote ( wholeText , offset - 1 , target , closingQuote )
59
+ newTextNode = replaceQuote ( range , offset - 1 , closingQuote )
60
+ } else if ( shouldBeOpeningQuote ( textArr , offset - 2 ) ) {
61
+ const openingQuote = isCharSingleQuote ? singleQuotes [ 0 ] : quotes [ 0 ]
62
+ newTextNode = replaceQuote ( range , offset - 1 , openingQuote )
35
63
}
36
64
37
- if ( isOpeningQuote ( wholeText , offset - 2 ) ) {
38
- const openingQuote = isCharSingleQuote ? singleQuotes [ 0 ] : quotes [ 0 ]
39
- applySmartQuote ( wholeText , offset - 1 , target , openingQuote )
65
+ if ( ! newTextNode ) {
66
+ return
40
67
}
41
68
42
- resetCursor ( )
69
+ // Resets the cursor
70
+ const window = target . ownerDocument . defaultView
71
+ const selection = window . getSelection ( )
72
+ selection . collapse ( newTextNode , offset )
43
73
}
0 commit comments