1
1
/**
2
2
* External dependencies
3
3
*/
4
- import { createElement } from 'preact' ;
4
+ import { createElement , Component } from 'preact' ;
5
5
import { repeat , reduce , truncate } from 'lodash' ;
6
6
7
7
/**
8
8
* Internal dependencies
9
9
*/
10
10
import Link from 'components/link' ;
11
+ import { getSelectedOffset } from 'lib/selection' ;
11
12
12
13
/**
13
14
* Zero width character used as stand-in for replaced character to preserve
@@ -17,18 +18,25 @@ import Link from 'components/link';
17
18
*/
18
19
const ZERO_WIDTH_SPACE = '' ;
19
20
20
- export default function DoneText ( { onClick, onFocus, onMouseDown, children } ) {
21
- const transforms = [ [
22
- / ( ^ | \s ) # ( \S + ) / ,
23
- ( [ , whitespace , tag ] ) => [
21
+ /**
22
+ * Tuples of transforms, first entry as a regular expression to match, second
23
+ * entry as a function return element to use in place of the original text.
24
+ *
25
+ * @type {Array }
26
+ */
27
+ const TRANSFORMS = [
28
+ {
29
+ pattern : / ( ^ | \s ) # ( \S + ) / ,
30
+ transform : ( [ , whitespace , tag ] ) => [
24
31
whitespace ,
25
32
< Link preload to = { `/tags/${ tag } /` } >
26
33
#{ tag }
27
34
</ Link > ,
28
35
] ,
29
- ] , [
30
- / ( ^ | \s ) ( h t t p s ? : \/ \/ \S + ) / ,
31
- ( [ , whitespace , url ] ) => {
36
+ } ,
37
+ {
38
+ pattern : / ( ^ | \s ) ( h t t p s ? : \/ \/ \S + ) / ,
39
+ transform : ( [ , whitespace , url ] ) => {
32
40
const [ prefix ] = url . match ( / ^ h t t p s ? : \/ \/ ( w w w \. ) ? / ) ;
33
41
const endRepeat = Math . max ( 0 , url . length - prefix . length - 30 ) ;
34
42
@@ -44,16 +52,25 @@ export default function DoneText( { onClick, onFocus, onMouseDown, children } )
44
52
</ Link > ,
45
53
] ;
46
54
} ,
47
- ] , [
48
- / ` ( [ ^ ` ] + ) ` / ,
49
- ( [ , code ] ) => (
55
+ } ,
56
+ {
57
+ pattern : / ` ( [ ^ ` ] + ) ` / ,
58
+ transform : ( [ , code ] ) => (
50
59
< code > { ZERO_WIDTH_SPACE } { code } { ZERO_WIDTH_SPACE } </ code >
51
60
) ,
52
- ] ] ;
61
+ } ,
62
+ ] ;
53
63
64
+ /**
65
+ * Given array of children, returns transformed children
66
+ *
67
+ * @param {Array } children Original children
68
+ * @return {Array } Transformed children
69
+ */
70
+ function getTransformedDoneText ( children ) {
54
71
let parts = [ ...children ] ;
55
72
56
- for ( const [ pattern , transform ] of transforms ) {
73
+ for ( const { pattern, transform } of TRANSFORMS ) {
57
74
parts = reduce ( parts , ( memo , part ) => {
58
75
if ( 'string' !== typeof part ) {
59
76
return memo . concat ( part ) ;
@@ -72,26 +89,59 @@ export default function DoneText( { onClick, onFocus, onMouseDown, children } )
72
89
} , [ ] ) ;
73
90
}
74
91
75
- // Infer focus handlers as intent to edit. Ensure element can receive focus
76
- // and apply ARIA role to indicate editability.
77
- let focusProps ;
78
- if ( onFocus ) {
79
- focusProps = {
80
- tabIndex : 0 ,
81
- role : 'textbox' ,
82
- onFocus,
83
- } ;
84
- }
92
+ return parts ;
93
+ }
85
94
86
- return (
87
- < div
88
- { ...focusProps }
89
- onClick = { onClick }
90
- onMouseDown = { onMouseDown }
91
- className = "done-text" >
92
- < div className = "done-text__overflow" >
93
- { parts }
95
+ export default class DoneText extends Component {
96
+ setCopyText = ( event ) => {
97
+ // Override the clipboard data with the original children text to avoid
98
+ // zero-width spaces, and because its generally more useful if planned
99
+ // to paste into a separate new done.
100
+
101
+ let setData ;
102
+ if ( window . clipboardData ) {
103
+ setData = ( text ) => window . clipboardData . setData ( 'Text' , text ) ;
104
+ } else if ( event . clipboardData ) {
105
+ setData = ( text ) => event . clipboardData . setData ( 'text/plain' , text ) ;
106
+ }
107
+
108
+ if ( ! setData ) {
109
+ return ;
110
+ }
111
+
112
+ const { children } = this . props ;
113
+ const [ start , end ] = getSelectedOffset ( this . node ) ;
114
+ const text = children . join ( '' ) . slice ( start , end ) ;
115
+ setData ( text ) ;
116
+ event . preventDefault ( ) ;
117
+ } ;
118
+
119
+ render ( ) {
120
+ const { onClick, onFocus, onMouseDown, children } = this . props ;
121
+
122
+ // Infer focus handlers as intent to edit. Ensure element can receive focus
123
+ // and apply ARIA role to indicate editability.
124
+ let focusProps ;
125
+ if ( onFocus ) {
126
+ focusProps = {
127
+ tabIndex : 0 ,
128
+ role : 'textbox' ,
129
+ onFocus,
130
+ } ;
131
+ }
132
+
133
+ return (
134
+ < div
135
+ ref = { ( node ) => this . node = node }
136
+ { ...focusProps }
137
+ onClick = { onClick }
138
+ onMouseDown = { onMouseDown }
139
+ onCopy = { this . setCopyText }
140
+ className = "done-text" >
141
+ < div className = "done-text__overflow" >
142
+ { getTransformedDoneText ( children ) }
143
+ </ div >
94
144
</ div >
95
- </ div >
96
- ) ;
145
+ ) ;
146
+ }
97
147
}
0 commit comments