balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

wplink.js (21159B)


      1 /**
      2  * @output wp-includes/js/wplink.js
      3  */
      4 
      5  /* global wpLink */
      6 
      7 ( function( $, wpLinkL10n, wp ) {
      8 	var editor, searchTimer, River, Query, correctedURL,
      9 		emailRegexp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i,
     10 		urlRegexp = /^(https?|ftp):\/\/[A-Z0-9.-]+\.[A-Z]{2,63}[^ "]*$/i,
     11 		inputs = {},
     12 		rivers = {},
     13 		isTouch = ( 'ontouchend' in document );
     14 
     15 	function getLink() {
     16 		if ( editor ) {
     17 			return editor.$( 'a[data-wplink-edit="true"]' );
     18 		}
     19 
     20 		return null;
     21 	}
     22 
     23 	window.wpLink = {
     24 		timeToTriggerRiver: 150,
     25 		minRiverAJAXDuration: 200,
     26 		riverBottomThreshold: 5,
     27 		keySensitivity: 100,
     28 		lastSearch: '',
     29 		textarea: '',
     30 		modalOpen: false,
     31 
     32 		init: function() {
     33 			inputs.wrap = $('#wp-link-wrap');
     34 			inputs.dialog = $( '#wp-link' );
     35 			inputs.backdrop = $( '#wp-link-backdrop' );
     36 			inputs.submit = $( '#wp-link-submit' );
     37 			inputs.close = $( '#wp-link-close' );
     38 
     39 			// Input.
     40 			inputs.text = $( '#wp-link-text' );
     41 			inputs.url = $( '#wp-link-url' );
     42 			inputs.nonce = $( '#_ajax_linking_nonce' );
     43 			inputs.openInNewTab = $( '#wp-link-target' );
     44 			inputs.search = $( '#wp-link-search' );
     45 
     46 			// Build rivers.
     47 			rivers.search = new River( $( '#search-results' ) );
     48 			rivers.recent = new River( $( '#most-recent-results' ) );
     49 			rivers.elements = inputs.dialog.find( '.query-results' );
     50 
     51 			// Get search notice text.
     52 			inputs.queryNotice = $( '#query-notice-message' );
     53 			inputs.queryNoticeTextDefault = inputs.queryNotice.find( '.query-notice-default' );
     54 			inputs.queryNoticeTextHint = inputs.queryNotice.find( '.query-notice-hint' );
     55 
     56 			// Bind event handlers.
     57 			inputs.dialog.on( 'keydown', wpLink.keydown );
     58 			inputs.dialog.on( 'keyup', wpLink.keyup );
     59 			inputs.submit.on( 'click', function( event ) {
     60 				event.preventDefault();
     61 				wpLink.update();
     62 			});
     63 
     64 			inputs.close.add( inputs.backdrop ).add( '#wp-link-cancel button' ).on( 'click', function( event ) {
     65 				event.preventDefault();
     66 				wpLink.close();
     67 			});
     68 
     69 			rivers.elements.on( 'river-select', wpLink.updateFields );
     70 
     71 			// Display 'hint' message when search field or 'query-results' box are focused.
     72 			inputs.search.on( 'focus.wplink', function() {
     73 				inputs.queryNoticeTextDefault.hide();
     74 				inputs.queryNoticeTextHint.removeClass( 'screen-reader-text' ).show();
     75 			} ).on( 'blur.wplink', function() {
     76 				inputs.queryNoticeTextDefault.show();
     77 				inputs.queryNoticeTextHint.addClass( 'screen-reader-text' ).hide();
     78 			} );
     79 
     80 			inputs.search.on( 'keyup input', function() {
     81 				window.clearTimeout( searchTimer );
     82 				searchTimer = window.setTimeout( function() {
     83 					wpLink.searchInternalLinks();
     84 				}, 500 );
     85 			});
     86 
     87 			inputs.url.on( 'paste', function() {
     88 				setTimeout( wpLink.correctURL, 0 );
     89 			} );
     90 
     91 			inputs.url.on( 'blur', wpLink.correctURL );
     92 		},
     93 
     94 		// If URL wasn't corrected last time and doesn't start with http:, https:, ? # or /, prepend http://.
     95 		correctURL: function () {
     96 			var url = inputs.url.val().trim();
     97 
     98 			if ( url && correctedURL !== url && ! /^(?:[a-z]+:|#|\?|\.|\/)/.test( url ) ) {
     99 				inputs.url.val( 'http://' + url );
    100 				correctedURL = url;
    101 			}
    102 		},
    103 
    104 		open: function( editorId, url, text ) {
    105 			var ed,
    106 				$body = $( document.body );
    107 
    108 			$body.addClass( 'modal-open' );
    109 			wpLink.modalOpen = true;
    110 
    111 			wpLink.range = null;
    112 
    113 			if ( editorId ) {
    114 				window.wpActiveEditor = editorId;
    115 			}
    116 
    117 			if ( ! window.wpActiveEditor ) {
    118 				return;
    119 			}
    120 
    121 			this.textarea = $( '#' + window.wpActiveEditor ).get( 0 );
    122 
    123 			if ( typeof window.tinymce !== 'undefined' ) {
    124 				// Make sure the link wrapper is the last element in the body,
    125 				// or the inline editor toolbar may show above the backdrop.
    126 				$body.append( inputs.backdrop, inputs.wrap );
    127 
    128 				ed = window.tinymce.get( window.wpActiveEditor );
    129 
    130 				if ( ed && ! ed.isHidden() ) {
    131 					editor = ed;
    132 				} else {
    133 					editor = null;
    134 				}
    135 			}
    136 
    137 			if ( ! wpLink.isMCE() && document.selection ) {
    138 				this.textarea.focus();
    139 				this.range = document.selection.createRange();
    140 			}
    141 
    142 			inputs.wrap.show();
    143 			inputs.backdrop.show();
    144 
    145 			wpLink.refresh( url, text );
    146 
    147 			$( document ).trigger( 'wplink-open', inputs.wrap );
    148 		},
    149 
    150 		isMCE: function() {
    151 			return editor && ! editor.isHidden();
    152 		},
    153 
    154 		refresh: function( url, text ) {
    155 			var linkText = '';
    156 
    157 			// Refresh rivers (clear links, check visibility).
    158 			rivers.search.refresh();
    159 			rivers.recent.refresh();
    160 
    161 			if ( wpLink.isMCE() ) {
    162 				wpLink.mceRefresh( url, text );
    163 			} else {
    164 				// For the Text editor the "Link text" field is always shown.
    165 				if ( ! inputs.wrap.hasClass( 'has-text-field' ) ) {
    166 					inputs.wrap.addClass( 'has-text-field' );
    167 				}
    168 
    169 				if ( document.selection ) {
    170 					// Old IE.
    171 					linkText = document.selection.createRange().text || text || '';
    172 				} else if ( typeof this.textarea.selectionStart !== 'undefined' &&
    173 					( this.textarea.selectionStart !== this.textarea.selectionEnd ) ) {
    174 					// W3C.
    175 					text = this.textarea.value.substring( this.textarea.selectionStart, this.textarea.selectionEnd ) || text || '';
    176 				}
    177 
    178 				inputs.text.val( text );
    179 				wpLink.setDefaultValues();
    180 			}
    181 
    182 			if ( isTouch ) {
    183 				// Close the onscreen keyboard.
    184 				inputs.url.trigger( 'focus' ).trigger( 'blur' );
    185 			} else {
    186 				/*
    187 				 * Focus the URL field and highlight its contents.
    188 				 * If this is moved above the selection changes,
    189 				 * IE will show a flashing cursor over the dialog.
    190 				 */
    191 				window.setTimeout( function() {
    192 					inputs.url[0].select();
    193 					inputs.url.trigger( 'focus' );
    194 				} );
    195 			}
    196 
    197 			// Load the most recent results if this is the first time opening the panel.
    198 			if ( ! rivers.recent.ul.children().length ) {
    199 				rivers.recent.ajax();
    200 			}
    201 
    202 			correctedURL = inputs.url.val().replace( /^http:\/\//, '' );
    203 		},
    204 
    205 		hasSelectedText: function( linkNode ) {
    206 			var node, nodes, i, html = editor.selection.getContent();
    207 
    208 			// Partial html and not a fully selected anchor element.
    209 			if ( /</.test( html ) && ( ! /^<a [^>]+>[^<]+<\/a>$/.test( html ) || html.indexOf('href=') === -1 ) ) {
    210 				return false;
    211 			}
    212 
    213 			if ( linkNode.length ) {
    214 				nodes = linkNode[0].childNodes;
    215 
    216 				if ( ! nodes || ! nodes.length ) {
    217 					return false;
    218 				}
    219 
    220 				for ( i = nodes.length - 1; i >= 0; i-- ) {
    221 					node = nodes[i];
    222 
    223 					if ( node.nodeType != 3 && ! window.tinymce.dom.BookmarkManager.isBookmarkNode( node ) ) {
    224 						return false;
    225 					}
    226 				}
    227 			}
    228 
    229 			return true;
    230 		},
    231 
    232 		mceRefresh: function( searchStr, text ) {
    233 			var linkText, href,
    234 				linkNode = getLink(),
    235 				onlyText = this.hasSelectedText( linkNode );
    236 
    237 			if ( linkNode.length ) {
    238 				linkText = linkNode.text();
    239 				href = linkNode.attr( 'href' );
    240 
    241 				if ( ! linkText.trim() ) {
    242 					linkText = text || '';
    243 				}
    244 
    245 				if ( searchStr && ( urlRegexp.test( searchStr ) || emailRegexp.test( searchStr ) ) ) {
    246 					href = searchStr;
    247 				}
    248 
    249 				if ( href !== '_wp_link_placeholder' ) {
    250 					inputs.url.val( href );
    251 					inputs.openInNewTab.prop( 'checked', '_blank' === linkNode.attr( 'target' ) );
    252 					inputs.submit.val( wpLinkL10n.update );
    253 				} else {
    254 					this.setDefaultValues( linkText );
    255 				}
    256 
    257 				if ( searchStr && searchStr !== href ) {
    258 					// The user has typed something in the inline dialog. Trigger a search with it.
    259 					inputs.search.val( searchStr );
    260 				} else {
    261 					inputs.search.val( '' );
    262 				}
    263 
    264 				// Always reset the search.
    265 				window.setTimeout( function() {
    266 					wpLink.searchInternalLinks();
    267 				} );
    268 			} else {
    269 				linkText = editor.selection.getContent({ format: 'text' }) || text || '';
    270 				this.setDefaultValues( linkText );
    271 			}
    272 
    273 			if ( onlyText ) {
    274 				inputs.text.val( linkText );
    275 				inputs.wrap.addClass( 'has-text-field' );
    276 			} else {
    277 				inputs.text.val( '' );
    278 				inputs.wrap.removeClass( 'has-text-field' );
    279 			}
    280 		},
    281 
    282 		close: function( reset ) {
    283 			$( document.body ).removeClass( 'modal-open' );
    284 			wpLink.modalOpen = false;
    285 
    286 			if ( reset !== 'noReset' ) {
    287 				if ( ! wpLink.isMCE() ) {
    288 					wpLink.textarea.focus();
    289 
    290 					if ( wpLink.range ) {
    291 						wpLink.range.moveToBookmark( wpLink.range.getBookmark() );
    292 						wpLink.range.select();
    293 					}
    294 				} else {
    295 					if ( editor.plugins.wplink ) {
    296 						editor.plugins.wplink.close();
    297 					}
    298 
    299 					editor.focus();
    300 				}
    301 			}
    302 
    303 			inputs.backdrop.hide();
    304 			inputs.wrap.hide();
    305 
    306 			correctedURL = false;
    307 
    308 			$( document ).trigger( 'wplink-close', inputs.wrap );
    309 		},
    310 
    311 		getAttrs: function() {
    312 			wpLink.correctURL();
    313 
    314 			return {
    315 				href: inputs.url.val().trim(),
    316 				target: inputs.openInNewTab.prop( 'checked' ) ? '_blank' : null
    317 			};
    318 		},
    319 
    320 		buildHtml: function(attrs) {
    321 			var html = '<a href="' + attrs.href + '"';
    322 
    323 			if ( attrs.target ) {
    324 				html += ' rel="noopener" target="' + attrs.target + '"';
    325 			}
    326 
    327 			return html + '>';
    328 		},
    329 
    330 		update: function() {
    331 			if ( wpLink.isMCE() ) {
    332 				wpLink.mceUpdate();
    333 			} else {
    334 				wpLink.htmlUpdate();
    335 			}
    336 		},
    337 
    338 		htmlUpdate: function() {
    339 			var attrs, text, html, begin, end, cursor, selection,
    340 				textarea = wpLink.textarea;
    341 
    342 			if ( ! textarea ) {
    343 				return;
    344 			}
    345 
    346 			attrs = wpLink.getAttrs();
    347 			text = inputs.text.val();
    348 
    349 			var parser = document.createElement( 'a' );
    350 			parser.href = attrs.href;
    351 
    352 			if ( 'javascript:' === parser.protocol || 'data:' === parser.protocol ) { // jshint ignore:line
    353 				attrs.href = '';
    354 			}
    355 
    356 			// If there's no href, return.
    357 			if ( ! attrs.href ) {
    358 				return;
    359 			}
    360 
    361 			html = wpLink.buildHtml(attrs);
    362 
    363 			// Insert HTML.
    364 			if ( document.selection && wpLink.range ) {
    365 				// IE.
    366 				// Note: If no text is selected, IE will not place the cursor
    367 				// inside the closing tag.
    368 				textarea.focus();
    369 				wpLink.range.text = html + ( text || wpLink.range.text ) + '</a>';
    370 				wpLink.range.moveToBookmark( wpLink.range.getBookmark() );
    371 				wpLink.range.select();
    372 
    373 				wpLink.range = null;
    374 			} else if ( typeof textarea.selectionStart !== 'undefined' ) {
    375 				// W3C.
    376 				begin = textarea.selectionStart;
    377 				end = textarea.selectionEnd;
    378 				selection = text || textarea.value.substring( begin, end );
    379 				html = html + selection + '</a>';
    380 				cursor = begin + html.length;
    381 
    382 				// If no text is selected, place the cursor inside the closing tag.
    383 				if ( begin === end && ! selection ) {
    384 					cursor -= 4;
    385 				}
    386 
    387 				textarea.value = (
    388 					textarea.value.substring( 0, begin ) +
    389 					html +
    390 					textarea.value.substring( end, textarea.value.length )
    391 				);
    392 
    393 				// Update cursor position.
    394 				textarea.selectionStart = textarea.selectionEnd = cursor;
    395 			}
    396 
    397 			wpLink.close();
    398 			textarea.focus();
    399 			$( textarea ).trigger( 'change' );
    400 
    401 			// Audible confirmation message when a link has been inserted in the Editor.
    402 			wp.a11y.speak( wpLinkL10n.linkInserted );
    403 		},
    404 
    405 		mceUpdate: function() {
    406 			var attrs = wpLink.getAttrs(),
    407 				$link, text, hasText;
    408 
    409 			var parser = document.createElement( 'a' );
    410 			parser.href = attrs.href;
    411 
    412 			if ( 'javascript:' === parser.protocol || 'data:' === parser.protocol ) { // jshint ignore:line
    413 				attrs.href = '';
    414 			}
    415 
    416 			if ( ! attrs.href ) {
    417 				editor.execCommand( 'unlink' );
    418 				wpLink.close();
    419 				return;
    420 			}
    421 
    422 			$link = getLink();
    423 
    424 			editor.undoManager.transact( function() {
    425 				if ( ! $link.length ) {
    426 					editor.execCommand( 'mceInsertLink', false, { href: '_wp_link_placeholder', 'data-wp-temp-link': 1 } );
    427 					$link = editor.$( 'a[data-wp-temp-link="1"]' ).removeAttr( 'data-wp-temp-link' );
    428 					hasText = $link.text().trim();
    429 				}
    430 
    431 				if ( ! $link.length ) {
    432 					editor.execCommand( 'unlink' );
    433 				} else {
    434 					if ( inputs.wrap.hasClass( 'has-text-field' ) ) {
    435 						text = inputs.text.val();
    436 
    437 						if ( text ) {
    438 							$link.text( text );
    439 						} else if ( ! hasText ) {
    440 							$link.text( attrs.href );
    441 						}
    442 					}
    443 
    444 					attrs['data-wplink-edit'] = null;
    445 					attrs['data-mce-href'] = attrs.href;
    446 					$link.attr( attrs );
    447 				}
    448 			} );
    449 
    450 			wpLink.close( 'noReset' );
    451 			editor.focus();
    452 
    453 			if ( $link.length ) {
    454 				editor.selection.select( $link[0] );
    455 
    456 				if ( editor.plugins.wplink ) {
    457 					editor.plugins.wplink.checkLink( $link[0] );
    458 				}
    459 			}
    460 
    461 			editor.nodeChanged();
    462 
    463 			// Audible confirmation message when a link has been inserted in the Editor.
    464 			wp.a11y.speak( wpLinkL10n.linkInserted );
    465 		},
    466 
    467 		updateFields: function( e, li ) {
    468 			inputs.url.val( li.children( '.item-permalink' ).val() );
    469 
    470 			if ( inputs.wrap.hasClass( 'has-text-field' ) && ! inputs.text.val() ) {
    471 				inputs.text.val( li.children( '.item-title' ).text() );
    472 			}
    473 		},
    474 
    475 		getUrlFromSelection: function( selection ) {
    476 			if ( ! selection ) {
    477 				if ( this.isMCE() ) {
    478 					selection = editor.selection.getContent({ format: 'text' });
    479 				} else if ( document.selection && wpLink.range ) {
    480 					selection = wpLink.range.text;
    481 				} else if ( typeof this.textarea.selectionStart !== 'undefined' ) {
    482 					selection = this.textarea.value.substring( this.textarea.selectionStart, this.textarea.selectionEnd );
    483 				}
    484 			}
    485 
    486 			selection = selection || '';
    487 			selection = selection.trim();
    488 
    489 			if ( selection && emailRegexp.test( selection ) ) {
    490 				// Selection is email address.
    491 				return 'mailto:' + selection;
    492 			} else if ( selection && urlRegexp.test( selection ) ) {
    493 				// Selection is URL.
    494 				return selection.replace( /&amp;|&#0?38;/gi, '&' );
    495 			}
    496 
    497 			return '';
    498 		},
    499 
    500 		setDefaultValues: function( selection ) {
    501 			inputs.url.val( this.getUrlFromSelection( selection ) );
    502 
    503 			// Empty the search field and swap the "rivers".
    504 			inputs.search.val('');
    505 			wpLink.searchInternalLinks();
    506 
    507 			// Update save prompt.
    508 			inputs.submit.val( wpLinkL10n.save );
    509 		},
    510 
    511 		searchInternalLinks: function() {
    512 			var waiting,
    513 				search = inputs.search.val() || '',
    514 				minInputLength = parseInt( wpLinkL10n.minInputLength, 10 ) || 3;
    515 
    516 			if ( search.length >= minInputLength ) {
    517 				rivers.recent.hide();
    518 				rivers.search.show();
    519 
    520 				// Don't search if the keypress didn't change the title.
    521 				if ( wpLink.lastSearch == search )
    522 					return;
    523 
    524 				wpLink.lastSearch = search;
    525 				waiting = inputs.search.parent().find( '.spinner' ).addClass( 'is-active' );
    526 
    527 				rivers.search.change( search );
    528 				rivers.search.ajax( function() {
    529 					waiting.removeClass( 'is-active' );
    530 				});
    531 			} else {
    532 				rivers.search.hide();
    533 				rivers.recent.show();
    534 			}
    535 		},
    536 
    537 		next: function() {
    538 			rivers.search.next();
    539 			rivers.recent.next();
    540 		},
    541 
    542 		prev: function() {
    543 			rivers.search.prev();
    544 			rivers.recent.prev();
    545 		},
    546 
    547 		keydown: function( event ) {
    548 			var fn, id;
    549 
    550 			// Escape key.
    551 			if ( 27 === event.keyCode ) {
    552 				wpLink.close();
    553 				event.stopImmediatePropagation();
    554 			// Tab key.
    555 			} else if ( 9 === event.keyCode ) {
    556 				id = event.target.id;
    557 
    558 				// wp-link-submit must always be the last focusable element in the dialog.
    559 				// Following focusable elements will be skipped on keyboard navigation.
    560 				if ( id === 'wp-link-submit' && ! event.shiftKey ) {
    561 					inputs.close.trigger( 'focus' );
    562 					event.preventDefault();
    563 				} else if ( id === 'wp-link-close' && event.shiftKey ) {
    564 					inputs.submit.trigger( 'focus' );
    565 					event.preventDefault();
    566 				}
    567 			}
    568 
    569 			// Up Arrow and Down Arrow keys.
    570 			if ( event.shiftKey || ( 38 !== event.keyCode && 40 !== event.keyCode ) ) {
    571 				return;
    572 			}
    573 
    574 			if ( document.activeElement &&
    575 				( document.activeElement.id === 'link-title-field' || document.activeElement.id === 'url-field' ) ) {
    576 				return;
    577 			}
    578 
    579 			// Up Arrow key.
    580 			fn = 38 === event.keyCode ? 'prev' : 'next';
    581 			clearInterval( wpLink.keyInterval );
    582 			wpLink[ fn ]();
    583 			wpLink.keyInterval = setInterval( wpLink[ fn ], wpLink.keySensitivity );
    584 			event.preventDefault();
    585 		},
    586 
    587 		keyup: function( event ) {
    588 			// Up Arrow and Down Arrow keys.
    589 			if ( 38 === event.keyCode || 40 === event.keyCode ) {
    590 				clearInterval( wpLink.keyInterval );
    591 				event.preventDefault();
    592 			}
    593 		},
    594 
    595 		delayedCallback: function( func, delay ) {
    596 			var timeoutTriggered, funcTriggered, funcArgs, funcContext;
    597 
    598 			if ( ! delay )
    599 				return func;
    600 
    601 			setTimeout( function() {
    602 				if ( funcTriggered )
    603 					return func.apply( funcContext, funcArgs );
    604 				// Otherwise, wait.
    605 				timeoutTriggered = true;
    606 			}, delay );
    607 
    608 			return function() {
    609 				if ( timeoutTriggered )
    610 					return func.apply( this, arguments );
    611 				// Otherwise, wait.
    612 				funcArgs = arguments;
    613 				funcContext = this;
    614 				funcTriggered = true;
    615 			};
    616 		}
    617 	};
    618 
    619 	River = function( element, search ) {
    620 		var self = this;
    621 		this.element = element;
    622 		this.ul = element.children( 'ul' );
    623 		this.contentHeight = element.children( '#link-selector-height' );
    624 		this.waiting = element.find('.river-waiting');
    625 
    626 		this.change( search );
    627 		this.refresh();
    628 
    629 		$( '#wp-link .query-results, #wp-link #link-selector' ).on( 'scroll', function() {
    630 			self.maybeLoad();
    631 		});
    632 		element.on( 'click', 'li', function( event ) {
    633 			self.select( $( this ), event );
    634 		});
    635 	};
    636 
    637 	$.extend( River.prototype, {
    638 		refresh: function() {
    639 			this.deselect();
    640 			this.visible = this.element.is( ':visible' );
    641 		},
    642 		show: function() {
    643 			if ( ! this.visible ) {
    644 				this.deselect();
    645 				this.element.show();
    646 				this.visible = true;
    647 			}
    648 		},
    649 		hide: function() {
    650 			this.element.hide();
    651 			this.visible = false;
    652 		},
    653 		// Selects a list item and triggers the river-select event.
    654 		select: function( li, event ) {
    655 			var liHeight, elHeight, liTop, elTop;
    656 
    657 			if ( li.hasClass( 'unselectable' ) || li == this.selected )
    658 				return;
    659 
    660 			this.deselect();
    661 			this.selected = li.addClass( 'selected' );
    662 			// Make sure the element is visible.
    663 			liHeight = li.outerHeight();
    664 			elHeight = this.element.height();
    665 			liTop = li.position().top;
    666 			elTop = this.element.scrollTop();
    667 
    668 			if ( liTop < 0 ) // Make first visible element.
    669 				this.element.scrollTop( elTop + liTop );
    670 			else if ( liTop + liHeight > elHeight ) // Make last visible element.
    671 				this.element.scrollTop( elTop + liTop - elHeight + liHeight );
    672 
    673 			// Trigger the river-select event.
    674 			this.element.trigger( 'river-select', [ li, event, this ] );
    675 		},
    676 		deselect: function() {
    677 			if ( this.selected )
    678 				this.selected.removeClass( 'selected' );
    679 			this.selected = false;
    680 		},
    681 		prev: function() {
    682 			if ( ! this.visible )
    683 				return;
    684 
    685 			var to;
    686 			if ( this.selected ) {
    687 				to = this.selected.prev( 'li' );
    688 				if ( to.length )
    689 					this.select( to );
    690 			}
    691 		},
    692 		next: function() {
    693 			if ( ! this.visible )
    694 				return;
    695 
    696 			var to = this.selected ? this.selected.next( 'li' ) : $( 'li:not(.unselectable):first', this.element );
    697 			if ( to.length )
    698 				this.select( to );
    699 		},
    700 		ajax: function( callback ) {
    701 			var self = this,
    702 				delay = this.query.page == 1 ? 0 : wpLink.minRiverAJAXDuration,
    703 				response = wpLink.delayedCallback( function( results, params ) {
    704 					self.process( results, params );
    705 					if ( callback )
    706 						callback( results, params );
    707 				}, delay );
    708 
    709 			this.query.ajax( response );
    710 		},
    711 		change: function( search ) {
    712 			if ( this.query && this._search == search )
    713 				return;
    714 
    715 			this._search = search;
    716 			this.query = new Query( search );
    717 			this.element.scrollTop( 0 );
    718 		},
    719 		process: function( results, params ) {
    720 			var list = '', alt = true, classes = '',
    721 				firstPage = params.page == 1;
    722 
    723 			if ( ! results ) {
    724 				if ( firstPage ) {
    725 					list += '<li class="unselectable no-matches-found"><span class="item-title"><em>' +
    726 						wpLinkL10n.noMatchesFound + '</em></span></li>';
    727 				}
    728 			} else {
    729 				$.each( results, function() {
    730 					classes = alt ? 'alternate' : '';
    731 					classes += this.title ? '' : ' no-title';
    732 					list += classes ? '<li class="' + classes + '">' : '<li>';
    733 					list += '<input type="hidden" class="item-permalink" value="' + this.permalink + '" />';
    734 					list += '<span class="item-title">';
    735 					list += this.title ? this.title : wpLinkL10n.noTitle;
    736 					list += '</span><span class="item-info">' + this.info + '</span></li>';
    737 					alt = ! alt;
    738 				});
    739 			}
    740 
    741 			this.ul[ firstPage ? 'html' : 'append' ]( list );
    742 		},
    743 		maybeLoad: function() {
    744 			var self = this,
    745 				el = this.element,
    746 				bottom = el.scrollTop() + el.height();
    747 
    748 			if ( ! this.query.ready() || bottom < this.contentHeight.height() - wpLink.riverBottomThreshold )
    749 				return;
    750 
    751 			setTimeout(function() {
    752 				var newTop = el.scrollTop(),
    753 					newBottom = newTop + el.height();
    754 
    755 				if ( ! self.query.ready() || newBottom < self.contentHeight.height() - wpLink.riverBottomThreshold )
    756 					return;
    757 
    758 				self.waiting.addClass( 'is-active' );
    759 				el.scrollTop( newTop + self.waiting.outerHeight() );
    760 
    761 				self.ajax( function() {
    762 					self.waiting.removeClass( 'is-active' );
    763 				});
    764 			}, wpLink.timeToTriggerRiver );
    765 		}
    766 	});
    767 
    768 	Query = function( search ) {
    769 		this.page = 1;
    770 		this.allLoaded = false;
    771 		this.querying = false;
    772 		this.search = search;
    773 	};
    774 
    775 	$.extend( Query.prototype, {
    776 		ready: function() {
    777 			return ! ( this.querying || this.allLoaded );
    778 		},
    779 		ajax: function( callback ) {
    780 			var self = this,
    781 				query = {
    782 					action : 'wp-link-ajax',
    783 					page : this.page,
    784 					'_ajax_linking_nonce' : inputs.nonce.val()
    785 				};
    786 
    787 			if ( this.search )
    788 				query.search = this.search;
    789 
    790 			this.querying = true;
    791 
    792 			$.post( window.ajaxurl, query, function( r ) {
    793 				self.page++;
    794 				self.querying = false;
    795 				self.allLoaded = ! r;
    796 				callback( r, query );
    797 			}, 'json' );
    798 		}
    799 	});
    800 
    801 	$( wpLink.init );
    802 })( jQuery, window.wpLinkL10n, window.wp );