PageRenderTime 96ms CodeModel.GetById 29ms app.highlight 61ms RepoModel.GetById 1ms app.codeStats 0ms

/HotelSpaWP/wp-includes/js/tinymce/plugins/wplink/plugin.js

https://bitbucket.org/Trulsh/personal-bootstrap-projects
JavaScript | 613 lines | 477 code | 97 blank | 39 comment | 98 complexity | 5bd6d9dcdfa64309a4f474e3c103c87d MD5 | raw file
  1( function( tinymce ) {
  2	tinymce.ui.WPLinkPreview = tinymce.ui.Control.extend( {
  3		url: '#',
  4		renderHtml: function() {
  5			return (
  6				'<div id="' + this._id + '" class="wp-link-preview">' +
  7					'<a href="' + this.url + '" target="_blank" tabindex="-1">' + this.url + '</a>' +
  8				'</div>'
  9			);
 10		},
 11		setURL: function( url ) {
 12			var index, lastIndex;
 13
 14			if ( this.url !== url ) {
 15				this.url = url;
 16
 17				url = window.decodeURIComponent( url );
 18
 19				url = url.replace( /^(?:https?:)?\/\/(?:www\.)?/, '' );
 20
 21				if ( ( index = url.indexOf( '?' ) ) !== -1 ) {
 22					url = url.slice( 0, index );
 23				}
 24
 25				if ( ( index = url.indexOf( '#' ) ) !== -1 ) {
 26					url = url.slice( 0, index );
 27				}
 28
 29				url = url.replace( /(?:index)?\.html$/, '' );
 30
 31				if ( url.charAt( url.length - 1 ) === '/' ) {
 32					url = url.slice( 0, -1 );
 33				}
 34
 35				// If nothing's left (maybe the URL was just a fragment), use the whole URL.
 36				if ( url === '' ) {
 37					url = this.url;
 38				}
 39
 40				// If the URL is longer that 40 chars, concatenate the beginning (after the domain) and ending with ...
 41				if ( url.length > 40 && ( index = url.indexOf( '/' ) ) !== -1 && ( lastIndex = url.lastIndexOf( '/' ) ) !== -1 && lastIndex !== index ) {
 42					// If the beginning + ending are shorter that 40 chars, show more of the ending
 43					if ( index + url.length - lastIndex < 40 ) {
 44						lastIndex = -( 40 - ( index + 1 ) );
 45					}
 46
 47					url = url.slice( 0, index + 1 ) + '\u2026' + url.slice( lastIndex );
 48				}
 49
 50				tinymce.$( this.getEl().firstChild ).attr( 'href', this.url ).text( url );
 51			}
 52		}
 53	} );
 54
 55	tinymce.ui.WPLinkInput = tinymce.ui.Control.extend( {
 56		renderHtml: function() {
 57			return (
 58				'<div id="' + this._id + '" class="wp-link-input">' +
 59					'<input type="text" value="" placeholder="' + tinymce.translate( 'Paste URL or type to search' ) + '" />' +
 60					'<input type="text" style="display:none" value="" />' +
 61				'</div>'
 62			);
 63		},
 64		setURL: function( url ) {
 65			this.getEl().firstChild.value = url;
 66		},
 67		getURL: function() {
 68			return tinymce.trim( this.getEl().firstChild.value );
 69		},
 70		getLinkText: function() {
 71			var text = this.getEl().firstChild.nextSibling.value;
 72
 73			if ( ! tinymce.trim( text ) ) {
 74				return '';
 75			}
 76
 77			return text.replace( /[\r\n\t ]+/g, ' ' );
 78		},
 79		reset: function() {
 80			var urlInput = this.getEl().firstChild;
 81
 82			urlInput.value = '';
 83			urlInput.nextSibling.value = '';
 84		}
 85	} );
 86
 87	tinymce.PluginManager.add( 'wplink', function( editor ) {
 88		var toolbar;
 89		var editToolbar;
 90		var previewInstance;
 91		var inputInstance;
 92		var linkNode;
 93		var doingUndoRedo;
 94		var doingUndoRedoTimer;
 95		var $ = window.jQuery;
 96		var emailRegex = /^(mailto:)?[a-z0-9._%+-]+@[a-z0-9][a-z0-9.-]*\.[a-z]{2,63}$/i;
 97		var urlRegex1 = /^https?:\/\/([^\s/?.#-][^\s\/?.#]*\.?)+(\/[^\s"]*)?$/i;
 98		var urlRegex2 = /^https?:\/\/[^\/]+\.[^\/]+($|\/)/i;
 99		var speak = ( typeof window.wp !== 'undefined' && window.wp.a11y && window.wp.a11y.speak ) ? window.wp.a11y.speak : function() {};
100		var hasLinkError = false;
101
102		function getSelectedLink() {
103			var href, html,
104				node = editor.selection.getNode(),
105				link = editor.dom.getParent( node, 'a[href]' );
106
107			if ( ! link ) {
108				html = editor.selection.getContent({ format: 'raw' });
109
110				if ( html && html.indexOf( '</a>' ) !== -1 ) {
111					href = html.match( /href="([^">]+)"/ );
112
113					if ( href && href[1] ) {
114						link = editor.$( 'a[href="' + href[1] + '"]', node )[0];
115					}
116
117					if ( link ) {
118						editor.selection.select( link );
119					}
120				}
121			}
122
123			return link;
124		}
125
126		function removePlaceholders() {
127			editor.$( 'a' ).each( function( i, element ) {
128				var $element = editor.$( element );
129
130				if ( $element.attr( 'href' ) === '_wp_link_placeholder' ) {
131					editor.dom.remove( element, true );
132				} else if ( $element.attr( 'data-wplink-edit' ) ) {
133					$element.attr( 'data-wplink-edit', null );
134				}
135			});
136		}
137
138		function removePlaceholderStrings( content, dataAttr ) {
139			return content.replace( /(<a [^>]+>)([\s\S]*?)<\/a>/g, function( all, tag, text ) {
140				if ( tag.indexOf( ' href="_wp_link_placeholder"' ) > -1 ) {
141					return text;
142				}
143
144				if ( dataAttr ) {
145					tag = tag.replace( / data-wplink-edit="true"/g, '' );
146				}
147
148				tag = tag.replace( / data-wplink-url-error="true"/g, '' );
149
150				return tag + text + '</a>';
151			});
152		}
153
154		function checkLink( node ) {
155			var $link = editor.$( node );
156			var href = $link.attr( 'href' );
157
158			if ( ! href || typeof $ === 'undefined' ) {
159				return;
160			}
161
162			hasLinkError = false;
163
164			if ( /^http/i.test( href ) && ( ! urlRegex1.test( href ) || ! urlRegex2.test( href ) ) ) {
165				hasLinkError = true;
166				$link.attr( 'data-wplink-url-error', 'true' );
167				speak( editor.translate( 'Warning: the link has been inserted but may have errors. Please test it.' ), 'assertive' );
168			} else {
169				$link.removeAttr( 'data-wplink-url-error' );
170			}
171		}
172
173		editor.on( 'preinit', function() {
174			if ( editor.wp && editor.wp._createToolbar ) {
175				toolbar = editor.wp._createToolbar( [
176					'wp_link_preview',
177					'wp_link_edit',
178					'wp_link_remove'
179				], true );
180
181				var editButtons = [
182					'wp_link_input',
183					'wp_link_apply'
184				];
185
186				if ( typeof window.wpLink !== 'undefined' ) {
187					editButtons.push( 'wp_link_advanced' );
188				}
189
190				editToolbar = editor.wp._createToolbar( editButtons, true );
191
192				editToolbar.on( 'show', function() {
193					if ( typeof window.wpLink === 'undefined' || ! window.wpLink.modalOpen ) {
194						window.setTimeout( function() {
195							var element = editToolbar.$el.find( 'input.ui-autocomplete-input' )[0],
196								selection = linkNode && ( linkNode.textContent || linkNode.innerText );
197
198							if ( element ) {
199								if ( ! element.value && selection && typeof window.wpLink !== 'undefined' ) {
200									element.value = window.wpLink.getUrlFromSelection( selection );
201								}
202
203								if ( ! doingUndoRedo ) {
204									element.focus();
205									element.select();
206								}
207							}
208						} );
209					}
210				} );
211
212				editToolbar.on( 'hide', function() {
213					if ( ! editToolbar.scrolling ) {
214						editor.execCommand( 'wp_link_cancel' );
215					}
216				} );
217			}
218		} );
219
220		editor.addCommand( 'WP_Link', function() {
221			if ( tinymce.Env.ie && tinymce.Env.ie < 10 && typeof window.wpLink !== 'undefined' ) {
222				window.wpLink.open( editor.id );
223				return;
224			}
225
226			linkNode = getSelectedLink();
227			editToolbar.tempHide = false;
228
229			if ( linkNode ) {
230				editor.dom.setAttribs( linkNode, { 'data-wplink-edit': true } );
231			} else {
232				removePlaceholders();
233				editor.execCommand( 'mceInsertLink', false, { href: '_wp_link_placeholder' } );
234
235				linkNode = editor.$( 'a[href="_wp_link_placeholder"]' )[0];
236				editor.nodeChanged();
237			}
238		} );
239
240		editor.addCommand( 'wp_link_apply', function() {
241			if ( editToolbar.scrolling ) {
242				return;
243			}
244
245			var href, text;
246
247			if ( linkNode ) {
248				href = inputInstance.getURL();
249				text = inputInstance.getLinkText();
250				editor.focus();
251
252				if ( ! href ) {
253					editor.dom.remove( linkNode, true );
254					return;
255				}
256
257				if ( ! /^(?:[a-z]+:|#|\?|\.|\/)/.test( href ) && ! emailRegex.test( href ) ) {
258					href = 'http://' + href;
259				}
260
261				editor.dom.setAttribs( linkNode, { href: href, 'data-wplink-edit': null } );
262
263				if ( ! tinymce.trim( linkNode.innerHTML ) ) {
264					editor.$( linkNode ).text( text || href );
265				}
266
267				checkLink( linkNode );
268			}
269
270			inputInstance.reset();
271			editor.nodeChanged();
272
273			// Audible confirmation message when a link has been inserted in the Editor.
274			if ( typeof window.wpLinkL10n !== 'undefined' && ! hasLinkError ) {
275				speak( window.wpLinkL10n.linkInserted );
276			}
277		} );
278
279		editor.addCommand( 'wp_link_cancel', function() {
280			if ( ! editToolbar.tempHide ) {
281				inputInstance.reset();
282				removePlaceholders();
283			}
284		} );
285
286		editor.addCommand( 'wp_unlink', function() {
287			editor.execCommand( 'unlink' );
288			editToolbar.tempHide = false;
289			editor.execCommand( 'wp_link_cancel' );
290		} );
291
292		// WP default shortcuts
293		editor.addShortcut( 'access+a', '', 'WP_Link' );
294		editor.addShortcut( 'access+s', '', 'wp_unlink' );
295		// The "de-facto standard" shortcut, see #27305
296		editor.addShortcut( 'meta+k', '', 'WP_Link' );
297
298		editor.addButton( 'link', {
299			icon: 'link',
300			tooltip: 'Insert/edit link',
301			cmd: 'WP_Link',
302			stateSelector: 'a[href]'
303		});
304
305		editor.addButton( 'unlink', {
306			icon: 'unlink',
307			tooltip: 'Remove link',
308			cmd: 'unlink'
309		});
310
311		editor.addMenuItem( 'link', {
312			icon: 'link',
313			text: 'Insert/edit link',
314			cmd: 'WP_Link',
315			stateSelector: 'a[href]',
316			context: 'insert',
317			prependToContext: true
318		});
319
320		editor.on( 'pastepreprocess', function( event ) {
321			var pastedStr = event.content,
322				regExp = /^(?:https?:)?\/\/\S+$/i;
323
324			if ( ! editor.selection.isCollapsed() && ! regExp.test( editor.selection.getContent() ) ) {
325				pastedStr = pastedStr.replace( /<[^>]+>/g, '' );
326				pastedStr = tinymce.trim( pastedStr );
327
328				if ( regExp.test( pastedStr ) ) {
329					editor.execCommand( 'mceInsertLink', false, {
330						href: editor.dom.decode( pastedStr )
331					} );
332
333					event.preventDefault();
334				}
335			}
336		} );
337
338		// Remove any remaining placeholders on saving.
339		editor.on( 'savecontent', function( event ) {
340			event.content = removePlaceholderStrings( event.content, true );
341		});
342
343		// Prevent adding undo levels on inserting link placeholder.
344		editor.on( 'BeforeAddUndo', function( event ) {
345			if ( event.lastLevel && event.lastLevel.content && event.level.content &&
346				event.lastLevel.content === removePlaceholderStrings( event.level.content ) ) {
347
348				event.preventDefault();
349			}
350		});
351
352		// When doing undo and redo with keyboard shortcuts (Ctrl|Cmd+Z, Ctrl|Cmd+Shift+Z, Ctrl|Cmd+Y),
353		// set a flag to not focus the inline dialog. The editor has to remain focused so the users can do consecutive undo/redo.
354		editor.on( 'keydown', function( event ) {
355			if ( event.keyCode === 27 ) { // Esc
356				editor.execCommand( 'wp_link_cancel' );
357			}
358
359			if ( event.altKey || ( tinymce.Env.mac && ( ! event.metaKey || event.ctrlKey ) ) ||
360				( ! tinymce.Env.mac && ! event.ctrlKey ) ) {
361
362				return;
363			}
364
365			if ( event.keyCode === 89 || event.keyCode === 90 ) { // Y or Z
366				doingUndoRedo = true;
367
368				window.clearTimeout( doingUndoRedoTimer );
369				doingUndoRedoTimer = window.setTimeout( function() {
370					doingUndoRedo = false;
371				}, 500 );
372			}
373		} );
374
375		editor.addButton( 'wp_link_preview', {
376			type: 'WPLinkPreview',
377			onPostRender: function() {
378				previewInstance = this;
379			}
380		} );
381
382		editor.addButton( 'wp_link_input', {
383			type: 'WPLinkInput',
384			onPostRender: function() {
385				var element = this.getEl(),
386					input = element.firstChild,
387					$input, cache, last;
388
389				inputInstance = this;
390
391				if ( $ && $.ui && $.ui.autocomplete ) {
392					$input = $( input );
393
394					$input.on( 'keydown', function() {
395						$input.removeAttr( 'aria-activedescendant' );
396					} )
397					.autocomplete( {
398						source: function( request, response ) {
399							if ( last === request.term ) {
400								response( cache );
401								return;
402							}
403
404							if ( /^https?:/.test( request.term ) || request.term.indexOf( '.' ) !== -1 ) {
405								return response();
406							}
407
408							$.post( window.ajaxurl, {
409								action: 'wp-link-ajax',
410								page: 1,
411								search: request.term,
412								_ajax_linking_nonce: $( '#_ajax_linking_nonce' ).val()
413							}, function( data ) {
414								cache = data;
415								response( data );
416							}, 'json' );
417
418							last = request.term;
419						},
420						focus: function( event, ui ) {
421							$input.attr( 'aria-activedescendant', 'mce-wp-autocomplete-' + ui.item.ID );
422							/*
423							 * Don't empty the URL input field, when using the arrow keys to
424							 * highlight items. See api.jqueryui.com/autocomplete/#event-focus
425							 */
426							event.preventDefault();
427						},
428						select: function( event, ui ) {
429							$input.val( ui.item.permalink );
430							$( element.firstChild.nextSibling ).val( ui.item.title );
431
432							if ( 9 === event.keyCode && typeof window.wpLinkL10n !== 'undefined' ) {
433								// Audible confirmation message when a link has been selected.
434								speak( window.wpLinkL10n.linkSelected );
435							}
436
437							return false;
438						},
439						open: function() {
440							$input.attr( 'aria-expanded', 'true' );
441							editToolbar.blockHide = true;
442						},
443						close: function() {
444							$input.attr( 'aria-expanded', 'false' );
445							editToolbar.blockHide = false;
446						},
447						minLength: 2,
448						position: {
449							my: 'left top+2'
450						},
451						messages: {
452							noResults: ( typeof window.uiAutocompleteL10n !== 'undefined' ) ? window.uiAutocompleteL10n.noResults : '',
453							results: function( number ) {
454								if ( typeof window.uiAutocompleteL10n !== 'undefined' ) {
455									if ( number > 1 ) {
456										return window.uiAutocompleteL10n.manyResults.replace( '%d', number );
457									}
458
459									return window.uiAutocompleteL10n.oneResult;
460								}
461							}
462						}
463					} ).autocomplete( 'instance' )._renderItem = function( ul, item ) {
464						return $( '<li role="option" id="mce-wp-autocomplete-' + item.ID + '">' )
465						.append( '<span>' + item.title + '</span>&nbsp;<span class="wp-editor-float-right">' + item.info + '</span>' )
466						.appendTo( ul );
467					};
468
469					$input.attr( {
470						'role': 'combobox',
471						'aria-autocomplete': 'list',
472						'aria-expanded': 'false',
473						'aria-owns': $input.autocomplete( 'widget' ).attr( 'id' )
474					} )
475					.on( 'focus', function() {
476						var inputValue = $input.val();
477						/*
478						 * Don't trigger a search if the URL field already has a link or is empty.
479						 * Also, avoids screen readers announce `No search results`.
480						 */
481						if ( inputValue && ! /^https?:/.test( inputValue ) ) {
482							$input.autocomplete( 'search' );
483						}
484					} )
485					// Returns a jQuery object containing the menu element.
486					.autocomplete( 'widget' )
487						.addClass( 'wplink-autocomplete' )
488						.attr( 'role', 'listbox' )
489						.removeAttr( 'tabindex' ) // Remove the `tabindex=0` attribute added by jQuery UI.
490						/*
491						 * Looks like Safari and VoiceOver need an `aria-selected` attribute. See ticket #33301.
492						 * The `menufocus` and `menublur` events are the same events used to add and remove
493						 * the `ui-state-focus` CSS class on the menu items. See jQuery UI Menu Widget.
494						 */
495						.on( 'menufocus', function( event, ui ) {
496							ui.item.attr( 'aria-selected', 'true' );
497						})
498						.on( 'menublur', function() {
499							/*
500							 * The `menublur` event returns an object where the item is `null`
501							 * so we need to find the active item with other means.
502							 */
503							$( this ).find( '[aria-selected="true"]' ).removeAttr( 'aria-selected' );
504						});
505				}
506
507				tinymce.$( input ).on( 'keydown', function( event ) {
508					if ( event.keyCode === 13 ) {
509						editor.execCommand( 'wp_link_apply' );
510						event.preventDefault();
511					}
512				} );
513			}
514		} );
515
516		editor.on( 'wptoolbar', function( event ) {
517			var linkNode = editor.dom.getParent( event.element, 'a' ),
518				$linkNode, href, edit;
519
520			if ( typeof window.wpLink !== 'undefined' && window.wpLink.modalOpen ) {
521				editToolbar.tempHide = true;
522				return;
523			}
524
525			editToolbar.tempHide = false;
526
527			if ( linkNode ) {
528				$linkNode = editor.$( linkNode );
529				href = $linkNode.attr( 'href' );
530				edit = $linkNode.attr( 'data-wplink-edit' );
531
532				if ( href === '_wp_link_placeholder' || edit ) {
533					if ( href !== '_wp_link_placeholder' && ! inputInstance.getURL() ) {
534						inputInstance.setURL( href );
535					}
536
537					event.element = linkNode;
538					event.toolbar = editToolbar;
539				} else if ( href && ! $linkNode.find( 'img' ).length ) {
540					previewInstance.setURL( href );
541					event.element = linkNode;
542					event.toolbar = toolbar;
543
544					if ( $linkNode.attr( 'data-wplink-url-error' ) === 'true' ) {
545						toolbar.$el.find( '.wp-link-preview a' ).addClass( 'wplink-url-error' );
546					} else {
547						toolbar.$el.find( '.wp-link-preview a' ).removeClass( 'wplink-url-error' );
548						hasLinkError = false;
549					}
550				}
551			} else if ( editToolbar.visible() ) {
552				editor.execCommand( 'wp_link_cancel' );
553			}
554		} );
555
556		editor.addButton( 'wp_link_edit', {
557			tooltip: 'Edit ', // trailing space is needed, used for context
558			icon: 'dashicon dashicons-edit',
559			cmd: 'WP_Link'
560		} );
561
562		editor.addButton( 'wp_link_remove', {
563			tooltip: 'Remove link',
564			icon: 'dashicon dashicons-editor-unlink',
565			cmd: 'wp_unlink'
566		} );
567
568		editor.addButton( 'wp_link_advanced', {
569			tooltip: 'Link options',
570			icon: 'dashicon dashicons-admin-generic',
571			onclick: function() {
572				if ( typeof window.wpLink !== 'undefined' ) {
573					var url = inputInstance.getURL() || null,
574						text = inputInstance.getLinkText() || null;
575
576					/*
577					 * Accessibility note: moving focus back to the editor confuses
578					 * screen readers. They will announce again the Editor ARIA role
579					 * `application` and the iframe `title` attribute.
580					 *
581					 * Unfortunately IE looses the selection when the editor iframe
582					 * looses focus, so without returning focus to the editor, the code
583					 * in the modal will not be able to get the selection, place the caret
584					 * at the same location, etc.
585					 */
586					if ( tinymce.Env.ie ) {
587						editor.focus(); // Needed for IE
588					}
589
590					window.wpLink.open( editor.id, url, text, linkNode );
591
592					editToolbar.tempHide = true;
593					inputInstance.reset();
594				}
595			}
596		} );
597
598		editor.addButton( 'wp_link_apply', {
599			tooltip: 'Apply',
600			icon: 'dashicon dashicons-editor-break',
601			cmd: 'wp_link_apply',
602			classes: 'widget btn primary'
603		} );
604
605		return {
606			close: function() {
607				editToolbar.tempHide = false;
608				editor.execCommand( 'wp_link_cancel' );
609			},
610			checkLink: checkLink
611		};
612	} );
613} )( window.tinymce );