]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/tinymce/plugins/wptextpattern/plugin.js
Wordpress 4.6-scripts
[autoinstalls/wordpress.git] / wp-includes / js / tinymce / plugins / wptextpattern / plugin.js
1 /**
2  * Text pattern plugin for TinyMCE
3  *
4  * @since 4.3.0
5  *
6  * This plugin can automatically format text patterns as you type. It includes two patterns:
7  *  - Unordered list (`* ` and `- `).
8  *  - Ordered list (`1. ` and `1) `).
9  *
10  * If the transformation in unwanted, the user can undo the change by pressing backspace,
11  * using the undo shortcut, or the undo button in the toolbar.
12  */
13 ( function( tinymce, setTimeout ) {
14         if ( tinymce.Env.ie && tinymce.Env.ie < 9 ) {
15                 return;
16         }
17
18         tinymce.PluginManager.add( 'wptextpattern', function( editor ) {
19                 var VK = tinymce.util.VK;
20
21                 var spacePatterns = [
22                         { regExp: /^[*-]\s/, cmd: 'InsertUnorderedList' },
23                         { regExp: /^1[.)]\s/, cmd: 'InsertOrderedList' }
24                 ];
25
26                 var enterPatterns = [
27                         { start: '##', format: 'h2' },
28                         { start: '###', format: 'h3' },
29                         { start: '####', format: 'h4' },
30                         { start: '#####', format: 'h5' },
31                         { start: '######', format: 'h6' },
32                         { start: '>', format: 'blockquote' },
33                         { regExp: /^(-){3,}$/, element: 'hr' }
34                 ];
35
36                 var inlinePatterns = [
37                         { start: '`', end: '`', format: 'code' }
38                 ];
39
40                 var canUndo;
41                 var chars = [];
42
43                 tinymce.each( inlinePatterns, function( pattern ) {
44                         tinymce.each( ( pattern.start + pattern.end ).split( '' ), function( c ) {
45                                 if ( tinymce.inArray( chars, c ) === -1 ) {
46                                         chars.push( c );
47                                 }
48                         } );
49                 } );
50
51                 editor.on( 'selectionchange', function() {
52                         canUndo = null;
53                 } );
54
55                 editor.on( 'keydown', function( event ) {
56                         if ( ( canUndo && event.keyCode === 27 /* ESCAPE */ ) || ( canUndo === 'space' && event.keyCode === VK.BACKSPACE ) ) {
57                                 editor.undoManager.undo();
58                                 event.preventDefault();
59                                 event.stopImmediatePropagation();
60                         }
61
62                         if ( event.keyCode === VK.ENTER && ! VK.modifierPressed( event ) ) {
63                                 enter();
64                         }
65
66                         // Wait for the browser to insert the character.
67                         if ( event.keyCode === VK.SPACEBAR && ! event.ctrlKey && ! event.metaKey && ! event.altKey ) {
68                                 setTimeout( space );
69                         } else if ( event.keyCode > 47 && ! ( event.keyCode >= 91 && event.keyCode <= 93 ) ) {
70                                 setTimeout( inline );
71                         }
72                 }, true );
73
74                 function inline() {
75                         var rng = editor.selection.getRng();
76                         var node = rng.startContainer;
77                         var offset = rng.startOffset;
78                         var startOffset;
79                         var endOffset;
80                         var pattern;
81                         var format;
82                         var zero;
83
84                         if ( ! node || node.nodeType !== 3 || ! node.data.length || ! offset ) {
85                                 return;
86                         }
87
88                         if ( tinymce.inArray( chars, node.data.charAt( offset - 1 ) ) === -1 ) {
89                                 return;
90                         }
91
92                         function findStart( node ) {
93                                 var i = inlinePatterns.length;
94                                 var offset;
95
96                                 while ( i-- ) {
97                                         pattern = inlinePatterns[ i ];
98                                         offset = node.data.indexOf( pattern.end );
99
100                                         if ( offset !== -1 ) {
101                                                 return offset;
102                                         }
103                                 }
104                         }
105
106                         startOffset = findStart( node );
107                         endOffset = node.data.lastIndexOf( pattern.end );
108
109                         if ( startOffset === endOffset || endOffset === -1 ) {
110                                 return;
111                         }
112
113                         if ( endOffset - startOffset <= pattern.start.length ) {
114                                 return;
115                         }
116
117                         if ( node.data.slice( startOffset + pattern.start.length, endOffset ).indexOf( pattern.start.slice( 0, 1 ) ) !== -1 ) {
118                                 return;
119                         }
120
121                         format = editor.formatter.get( pattern.format );
122
123                         if ( format && format[0].inline ) {
124                                 editor.undoManager.add();
125
126                                 editor.undoManager.transact( function() {
127                                         node.insertData( offset, '\u200b' );
128
129                                         node = node.splitText( startOffset );
130                                         zero = node.splitText( offset - startOffset );
131
132                                         node.deleteData( 0, pattern.start.length );
133                                         node.deleteData( node.data.length - pattern.end.length, pattern.end.length );
134
135                                         editor.formatter.apply( pattern.format, {}, node );
136
137                                         editor.selection.setCursorLocation( zero, 1 );
138                                 } );
139
140                                 // We need to wait for native events to be triggered.
141                                 setTimeout( function() {
142                                         canUndo = 'space';
143
144                                         editor.once( 'selectionchange', function() {
145                                                 var offset;
146
147                                                 if ( zero ) {
148                                                         offset = zero.data.indexOf( '\u200b' );
149
150                                                         if ( offset !== -1 ) {
151                                                                 zero.deleteData( offset, offset + 1 );
152                                                         }
153                                                 }
154                                         } );
155                                 } );
156                         }
157                 }
158
159                 function firstTextNode( node ) {
160                         var parent = editor.dom.getParent( node, 'p' ),
161                                 child;
162
163                         if ( ! parent ) {
164                                 return;
165                         }
166
167                         while ( child = parent.firstChild ) {
168                                 if ( child.nodeType !== 3 ) {
169                                         parent = child;
170                                 } else {
171                                         break;
172                                 }
173                         }
174
175                         if ( ! child ) {
176                                 return;
177                         }
178
179                         if ( ! child.data ) {
180                                 if ( child.nextSibling && child.nextSibling.nodeType === 3 ) {
181                                         child = child.nextSibling;
182                                 } else {
183                                         child = null;
184                                 }
185                         }
186
187                         return child;
188                 }
189
190                 function space() {
191                         var rng = editor.selection.getRng(),
192                                 node = rng.startContainer,
193                                 parent,
194                                 text;
195
196                         if ( ! node || firstTextNode( node ) !== node ) {
197                                 return;
198                         }
199
200                         parent = node.parentNode;
201                         text = node.data;
202
203                         tinymce.each( spacePatterns, function( pattern ) {
204                                 var match = text.match( pattern.regExp );
205
206                                 if ( ! match || rng.startOffset !== match[0].length ) {
207                                         return;
208                                 }
209
210                                 editor.undoManager.add();
211
212                                 editor.undoManager.transact( function() {
213                                         node.deleteData( 0, match[0].length );
214
215                                         if ( ! parent.innerHTML ) {
216                                                 parent.appendChild( document.createElement( 'br' ) );
217                                         }
218
219                                         editor.selection.setCursorLocation( parent );
220                                         editor.execCommand( pattern.cmd );
221                                 } );
222
223                                 // We need to wait for native events to be triggered.
224                                 setTimeout( function() {
225                                         canUndo = 'space';
226                                 } );
227
228                                 return false;
229                         } );
230                 }
231
232                 function enter() {
233                         var rng = editor.selection.getRng(),
234                                 start = rng.startContainer,
235                                 node = firstTextNode( start ),
236                                 i = enterPatterns.length,
237                                 text, pattern, parent;
238
239                         if ( ! node ) {
240                                 return;
241                         }
242
243                         text = node.data;
244
245                         while ( i-- ) {
246                                 if ( enterPatterns[ i ].start ) {
247                                         if ( text.indexOf( enterPatterns[ i ].start ) === 0 ) {
248                                                 pattern = enterPatterns[ i ];
249                                                 break;
250                                         }
251                                 } else if ( enterPatterns[ i ].regExp ) {
252                                         if ( enterPatterns[ i ].regExp.test( text ) ) {
253                                                 pattern = enterPatterns[ i ];
254                                                 break;
255                                         }
256                                 }
257                         }
258
259                         if ( ! pattern ) {
260                                 return;
261                         }
262
263                         if ( node === start && tinymce.trim( text ) === pattern.start ) {
264                                 return;
265                         }
266
267                         editor.once( 'keyup', function() {
268                                 editor.undoManager.add();
269
270                                 editor.undoManager.transact( function() {
271                                         if ( pattern.format ) {
272                                                 editor.formatter.apply( pattern.format, {}, node );
273                                                 node.replaceData( 0, node.data.length, ltrim( node.data.slice( pattern.start.length ) ) );
274                                         } else if ( pattern.element ) {
275                                                 parent = node.parentNode && node.parentNode.parentNode;
276
277                                                 if ( parent ) {
278                                                         parent.replaceChild( document.createElement( pattern.element ), node.parentNode );
279                                                 }
280                                         }
281                                 } );
282
283                                 // We need to wait for native events to be triggered.
284                                 setTimeout( function() {
285                                         canUndo = 'enter';
286                                 } );
287                         } );
288                 }
289
290                 function ltrim( text ) {
291                         return text ? text.replace( /^\s+/, '' ) : '';
292                 }
293         } );
294 } )( window.tinymce, window.setTimeout );