[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
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 several groups of patterns. 7 * 8 * Start of line patterns: 9 * As-you-type: 10 * - Unordered list (`* ` and `- `). 11 * - Ordered list (`1. ` and `1) `). 12 * 13 * On enter: 14 * - h2 (## ). 15 * - h3 (### ). 16 * - h4 (#### ). 17 * - h5 (##### ). 18 * - h6 (###### ). 19 * - blockquote (> ). 20 * - hr (---). 21 * 22 * Inline patterns: 23 * - <code> (`) (backtick). 24 * 25 * If the transformation in unwanted, the user can undo the change by pressing backspace, 26 * using the undo shortcut, or the undo button in the toolbar. 27 * 28 * Setting for the patterns can be overridden by plugins by using the `tiny_mce_before_init` PHP filter. 29 * The setting name is `wptextpattern` and the value is an object containing override arrays for each 30 * patterns group. There are three groups: "space", "enter", and "inline". Example (PHP): 31 * 32 * add_filter( 'tiny_mce_before_init', 'my_mce_init_wptextpattern' ); 33 * function my_mce_init_wptextpattern( $init ) { 34 * $init['wptextpattern'] = wp_json_encode( array( 35 * 'inline' => array( 36 * array( 'delimiter' => '**', 'format' => 'bold' ), 37 * array( 'delimiter' => '__', 'format' => 'italic' ), 38 * ), 39 * ) ); 40 * 41 * return $init; 42 * } 43 * 44 * Note that setting this will override the default text patterns. You will need to include them 45 * in your settings array if you want to keep them working. 46 */ 47 ( function( tinymce, setTimeout ) { 48 if ( tinymce.Env.ie && tinymce.Env.ie < 9 ) { 49 return; 50 } 51 52 /** 53 * Escapes characters for use in a Regular Expression. 54 * 55 * @param {String} string Characters to escape 56 * 57 * @return {String} Escaped characters 58 */ 59 function escapeRegExp( string ) { 60 return string.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ); 61 } 62 63 tinymce.PluginManager.add( 'wptextpattern', function( editor ) { 64 var VK = tinymce.util.VK; 65 var settings = editor.settings.wptextpattern || {}; 66 67 var spacePatterns = settings.space || [ 68 { regExp: /^[*-]\s/, cmd: 'InsertUnorderedList' }, 69 { regExp: /^1[.)]\s/, cmd: 'InsertOrderedList' } 70 ]; 71 72 var enterPatterns = settings.enter || [ 73 { start: '##', format: 'h2' }, 74 { start: '###', format: 'h3' }, 75 { start: '####', format: 'h4' }, 76 { start: '#####', format: 'h5' }, 77 { start: '######', format: 'h6' }, 78 { start: '>', format: 'blockquote' }, 79 { regExp: /^(-){3,}$/, element: 'hr' } 80 ]; 81 82 var inlinePatterns = settings.inline || [ 83 { delimiter: '`', format: 'code' } 84 ]; 85 86 var canUndo; 87 88 editor.on( 'selectionchange', function() { 89 canUndo = null; 90 } ); 91 92 editor.on( 'keydown', function( event ) { 93 if ( ( canUndo && event.keyCode === 27 /* ESCAPE */ ) || ( canUndo === 'space' && event.keyCode === VK.BACKSPACE ) ) { 94 editor.undoManager.undo(); 95 event.preventDefault(); 96 event.stopImmediatePropagation(); 97 } 98 99 if ( VK.metaKeyPressed( event ) ) { 100 return; 101 } 102 103 if ( event.keyCode === VK.ENTER ) { 104 enter(); 105 // Wait for the browser to insert the character. 106 } else if ( event.keyCode === VK.SPACEBAR ) { 107 setTimeout( space ); 108 } else if ( event.keyCode > 47 && ! ( event.keyCode >= 91 && event.keyCode <= 93 ) ) { 109 setTimeout( inline ); 110 } 111 }, true ); 112 113 function inline() { 114 var rng = editor.selection.getRng(); 115 var node = rng.startContainer; 116 var offset = rng.startOffset; 117 var startOffset; 118 var endOffset; 119 var pattern; 120 var format; 121 var zero; 122 123 // We need a non-empty text node with an offset greater than zero. 124 if ( ! node || node.nodeType !== 3 || ! node.data.length || ! offset ) { 125 return; 126 } 127 128 var string = node.data.slice( 0, offset ); 129 var lastChar = node.data.charAt( offset - 1 ); 130 131 tinymce.each( inlinePatterns, function( p ) { 132 // Character before selection should be delimiter. 133 if ( lastChar !== p.delimiter.slice( -1 ) ) { 134 return; 135 } 136 137 var escDelimiter = escapeRegExp( p.delimiter ); 138 var delimiterFirstChar = p.delimiter.charAt( 0 ); 139 var regExp = new RegExp( '(.*)' + escDelimiter + '.+' + escDelimiter + '$' ); 140 var match = string.match( regExp ); 141 142 if ( ! match ) { 143 return; 144 } 145 146 startOffset = match[1].length; 147 endOffset = offset - p.delimiter.length; 148 149 var before = string.charAt( startOffset - 1 ); 150 var after = string.charAt( startOffset + p.delimiter.length ); 151 152 // test*test* => format applied. 153 // test *test* => applied. 154 // test* test* => not applied. 155 if ( startOffset && /\S/.test( before ) ) { 156 if ( /\s/.test( after ) || before === delimiterFirstChar ) { 157 return; 158 } 159 } 160 161 // Do not replace when only whitespace and delimiter characters. 162 if ( ( new RegExp( '^[\\s' + escapeRegExp( delimiterFirstChar ) + ']+$' ) ).test( string.slice( startOffset, endOffset ) ) ) { 163 return; 164 } 165 166 pattern = p; 167 168 return false; 169 } ); 170 171 if ( ! pattern ) { 172 return; 173 } 174 175 format = editor.formatter.get( pattern.format ); 176 177 if ( format && format[0].inline ) { 178 editor.undoManager.add(); 179 180 editor.undoManager.transact( function() { 181 node.insertData( offset, '\uFEFF' ); 182 183 node = node.splitText( startOffset ); 184 zero = node.splitText( offset - startOffset ); 185 186 node.deleteData( 0, pattern.delimiter.length ); 187 node.deleteData( node.data.length - pattern.delimiter.length, pattern.delimiter.length ); 188 189 editor.formatter.apply( pattern.format, {}, node ); 190 191 editor.selection.setCursorLocation( zero, 1 ); 192 } ); 193 194 // We need to wait for native events to be triggered. 195 setTimeout( function() { 196 canUndo = 'space'; 197 198 editor.once( 'selectionchange', function() { 199 var offset; 200 201 if ( zero ) { 202 offset = zero.data.indexOf( '\uFEFF' ); 203 204 if ( offset !== -1 ) { 205 zero.deleteData( offset, offset + 1 ); 206 } 207 } 208 } ); 209 } ); 210 } 211 } 212 213 function firstTextNode( node ) { 214 var parent = editor.dom.getParent( node, 'p' ), 215 child; 216 217 if ( ! parent ) { 218 return; 219 } 220 221 while ( child = parent.firstChild ) { 222 if ( child.nodeType !== 3 ) { 223 parent = child; 224 } else { 225 break; 226 } 227 } 228 229 if ( ! child ) { 230 return; 231 } 232 233 if ( ! child.data ) { 234 if ( child.nextSibling && child.nextSibling.nodeType === 3 ) { 235 child = child.nextSibling; 236 } else { 237 child = null; 238 } 239 } 240 241 return child; 242 } 243 244 function space() { 245 var rng = editor.selection.getRng(), 246 node = rng.startContainer, 247 parent, 248 text; 249 250 if ( ! node || firstTextNode( node ) !== node ) { 251 return; 252 } 253 254 parent = node.parentNode; 255 text = node.data; 256 257 tinymce.each( spacePatterns, function( pattern ) { 258 var match = text.match( pattern.regExp ); 259 260 if ( ! match || rng.startOffset !== match[0].length ) { 261 return; 262 } 263 264 editor.undoManager.add(); 265 266 editor.undoManager.transact( function() { 267 node.deleteData( 0, match[0].length ); 268 269 if ( ! parent.innerHTML ) { 270 parent.appendChild( document.createElement( 'br' ) ); 271 } 272 273 editor.selection.setCursorLocation( parent ); 274 editor.execCommand( pattern.cmd ); 275 } ); 276 277 // We need to wait for native events to be triggered. 278 setTimeout( function() { 279 canUndo = 'space'; 280 } ); 281 282 return false; 283 } ); 284 } 285 286 function enter() { 287 var rng = editor.selection.getRng(), 288 start = rng.startContainer, 289 node = firstTextNode( start ), 290 i = enterPatterns.length, 291 text, pattern, parent; 292 293 if ( ! node ) { 294 return; 295 } 296 297 text = node.data; 298 299 while ( i-- ) { 300 if ( enterPatterns[ i ].start ) { 301 if ( text.indexOf( enterPatterns[ i ].start ) === 0 ) { 302 pattern = enterPatterns[ i ]; 303 break; 304 } 305 } else if ( enterPatterns[ i ].regExp ) { 306 if ( enterPatterns[ i ].regExp.test( text ) ) { 307 pattern = enterPatterns[ i ]; 308 break; 309 } 310 } 311 } 312 313 if ( ! pattern ) { 314 return; 315 } 316 317 if ( node === start && tinymce.trim( text ) === pattern.start ) { 318 return; 319 } 320 321 editor.once( 'keyup', function() { 322 editor.undoManager.add(); 323 324 editor.undoManager.transact( function() { 325 if ( pattern.format ) { 326 editor.formatter.apply( pattern.format, {}, node ); 327 node.replaceData( 0, node.data.length, ltrim( node.data.slice( pattern.start.length ) ) ); 328 } else if ( pattern.element ) { 329 parent = node.parentNode && node.parentNode.parentNode; 330 331 if ( parent ) { 332 parent.replaceChild( document.createElement( pattern.element ), node.parentNode ); 333 } 334 } 335 } ); 336 337 // We need to wait for native events to be triggered. 338 setTimeout( function() { 339 canUndo = 'enter'; 340 } ); 341 } ); 342 } 343 344 function ltrim( text ) { 345 return text ? text.replace( /^\s+/, '' ) : ''; 346 } 347 } ); 348 } )( window.tinymce, window.setTimeout );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |