home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / js / wplink.js < prev    next >
Encoding:
JavaScript  |  2017-09-19  |  20.5 KB  |  795 lines

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