Make WordPress Core

Ticket #36359: 36359.6.patch

File 36359.6.patch, 23.5 KB (added by azaozz, 10 years ago)
  • src/wp-includes/class-wp-editor.php

     
    14121412                <button type="button" id="wp-link-close"><span class="screen-reader-text"><?php _e( 'Close' ); ?></span></button>
    14131413                <div id="link-selector">
    14141414                        <div id="link-options">
     1415                                <p class="howto"><?php _e( 'Enter the destination URL' ); ?></p>
    14151416                                <div>
    14161417                                        <label><span><?php _e( 'URL' ); ?></span>
    1417                                         <input id="wp-link-url" type="text" role="combobox" aria-autocomplete="list" aria-expanded="false" placeholder="<?php _e( 'Paste URL or type to search' ); ?>" /></label>
     1418                                        <input id="wp-link-url" type="text" /></label>
    14181419                                </div>
    14191420                                <div class="wp-link-text-field">
    14201421                                        <label><span><?php _e( 'Link Text' ); ?></span>
     
    14251426                                        <input type="checkbox" id="wp-link-target" /> <?php _e( 'Open link in a new tab' ); ?></label>
    14261427                                </div>
    14271428                        </div>
     1429                        <p class="howto"><?php _e( 'Or link to existing content' ); ?></p>
     1430                        <div id="search-panel">
     1431                                <div class="link-search-wrapper">
     1432                                        <label>
     1433                                                <span class="search-label"><?php _e( 'Search' ); ?></span>
     1434                                                <input type="search" id="wp-link-search" class="link-search-field" autocomplete="off" />
     1435                                                <span class="spinner"></span>
     1436                                        </label>
     1437                                </div>
     1438                                <div id="search-results" class="query-results" tabindex="0">
     1439                                        <ul></ul>
     1440                                        <div class="river-waiting">
     1441                                                <span class="spinner"></span>
     1442                                        </div>
     1443                                </div>
     1444                                <div id="most-recent-results" class="query-results" tabindex="0">
     1445                                        <div class="query-notice" id="query-notice-message">
     1446                                                <em class="query-notice-default"><?php _e( 'No search term specified. Showing recent items.' ); ?></em>
     1447                                                <em class="query-notice-hint screen-reader-text"><?php _e( 'Search or use up and down arrow keys to select an item.' ); ?></em>
     1448                                        </div>
     1449                                        <ul></ul>
     1450                                        <div class="river-waiting">
     1451                                                <span class="spinner"></span>
     1452                                        </div>
     1453                                </div>
     1454                        </div>
    14281455                </div>
    14291456                <div class="submitbox">
    14301457                        <div id="wp-link-cancel">
  • src/wp-includes/css/editor.css

     
    9595}
    9696
    9797.mce-textbox,
    98 .mce-checkbox i.mce-i-checkbox {
     98.mce-checkbox i.mce-i-checkbox,
     99#wp-link .query-results {
    99100        border: 1px solid #ddd;
    100101        -webkit-border-radius: 0;
    101102        border-radius: 0;
     
    13741375        height: 100%;
    13751376}
    13761377
     1378#wp-link-wrap {
     1379        height: 500px;
     1380        margin-top: -250px;
     1381}
     1382
    13771383#wp-link-wrap .wp-link-text-field {
    13781384        display: none;
    13791385}
     
    14331439                0 0 2px 1px rgba(30, 140, 190, .8);
    14341440}
    14351441
    1436 #link-selector {
    1437         padding: 0 16px 50px;
     1442#wp-link-wrap #link-selector {
     1443        -webkit-overflow-scrolling: touch;
     1444        padding: 0 16px;
     1445        position: absolute;
     1446        top: 37px;
     1447        left: 0;
     1448        right: 0;
     1449        bottom: 44px;
    14381450}
    14391451
    14401452#wp-link ol,
     
    14641476}
    14651477
    14661478#wp-link label input[type="text"] {
    1467         margin: 8px 0 0;
     1479        margin-top: 5px;
    14681480        width: 70%;
    14691481}
    14701482
    1471 #wp-link #link-options label span {
     1483#wp-link #link-options label span,
     1484#wp-link #search-panel label span.search-label {
    14721485        display: inline-block;
    14731486        width: 80px;
    14741487        text-align: right;
     
    14781491        word-wrap: break-word;
    14791492}
    14801493
     1494#wp-link .link-search-field {
     1495        float: left;
     1496        width: 250px;
     1497        max-width: 70%;
     1498}
     1499
     1500#wp-link .link-search-wrapper {
     1501        margin: 5px 0 9px;
     1502        display: block;
     1503        overflow: hidden;
     1504}
     1505
     1506#wp-link .link-search-wrapper span {
     1507        float: left;
     1508        margin-top: 4px;
     1509}
     1510
     1511#wp-link .link-search-wrapper .spinner {
     1512        margin-top: 5px;
     1513}
     1514
    14811515#wp-link .link-target {
    14821516        padding: 3px 0 0;
    14831517        white-space: nowrap;
     
    14891523        max-width: 70%;
    14901524}
    14911525
     1526#wp-link .query-results {
     1527        border: 1px #dfdfdf solid;
     1528        margin: 0 0 12px;
     1529        background: #fff;
     1530        overflow: auto;
     1531        position: absolute;
     1532        left: 16px;
     1533        right: 16px;
     1534        bottom: 0;
     1535        top: 166px;
     1536}
     1537
     1538.has-text-field #wp-link .query-results {
     1539        top: 200px;
     1540}
     1541
     1542#wp-link li {
     1543        clear: both;
     1544        margin-bottom: 0;
     1545        border-bottom: 1px solid #f1f1f1;
     1546        color: #32373c;
     1547        padding: 4px 6px 4px 10px;
     1548        cursor: pointer;
     1549        position: relative;
     1550}
     1551
     1552#wp-link .query-notice {
     1553        padding: 0;
     1554        border-bottom: 1px solid #dfdfdf;
     1555        background-color: #f7fcfe;
     1556        color: #000;
     1557}
     1558
     1559#wp-link .query-notice .query-notice-default,
     1560#wp-link .query-notice .query-notice-hint {
     1561        display: block;
     1562        padding: 6px;
     1563        border-left: 4px solid #00a0d2;
     1564}
     1565
     1566#wp-link .unselectable.no-matches-found {
     1567        padding: 0;
     1568        border-bottom: 1px solid #dfdfdf;
     1569        background-color: #fef7f1;
     1570}
     1571
     1572#wp-link .no-matches-found .item-title {
     1573        display: block;
     1574        padding: 6px;
     1575        border-left: 4px solid #d54e21;
     1576}
     1577
     1578#wp-link .query-results em {
     1579        font-style: normal;
     1580}
     1581
     1582#wp-link li:hover {
     1583        background: #eaf2fa;
     1584        color: #151515;
     1585}
     1586
     1587#wp-link li.unselectable {
     1588        border-bottom: 1px solid #dfdfdf;
     1589}
     1590
     1591#wp-link li.unselectable:hover {
     1592        background: #fff;
     1593        cursor: auto;
     1594        color: #32373c;
     1595}
     1596
     1597#wp-link li.selected {
     1598        background: #ddd;
     1599        color: #32373c;
     1600}
     1601
     1602#wp-link li.selected .item-title {
     1603        font-weight: bold;
     1604}
     1605
     1606#wp-link li:last-child {
     1607        border: none;
     1608}
     1609
     1610#wp-link .item-title {
     1611        display: inline-block;
     1612        width: 80%;
     1613        width: -webkit-calc(100% - 68px);
     1614        width: calc(100% - 68px);
     1615        word-wrap: break-word;
     1616}
     1617
     1618#wp-link .item-info {
     1619        text-transform: uppercase;
     1620        color: #666;
     1621        font-size: 11px;
     1622        position: absolute;
     1623        right: 5px;
     1624        top: 5px;
     1625}
     1626
     1627#wp-link .river-waiting {
     1628        display: none;
     1629        padding: 10px 0;
     1630}
     1631
    14921632#wp-link .submitbox {
    14931633        padding: 8px 16px;
    14941634        background: #fcfcfc;
     
    15181658                margin-top: -140px;
    15191659        }
    15201660
     1661        #wp-link-wrap .query-results {
     1662                top: 195px;
     1663        }
     1664
     1665        #wp-link-wrap.has-text-field .query-results {
     1666                top: 235px;
     1667        }
     1668
    15211669        #link-selector {
    15221670                padding: 0 16px 60px;
    15231671        }
    15241672
     1673        #wp-link-wrap #link-selector {
     1674                bottom: 52px;
     1675        }
     1676
    15251677        #wp-link-cancel {
    15261678                line-height: 32px;
    15271679        }
     
    15491701        #wp-link-wrap {
    15501702                -webkit-transition: none;
    15511703                transition: none;
     1704                height: auto;
     1705                margin-top: 0;
     1706                top: 10px;
     1707                bottom: 10px;
    15521708        }
     1709
     1710        #link-selector {
     1711                overflow: auto;
     1712        }
     1713
     1714        #search-panel .query-results {
     1715                position: static;
     1716        }
    15531717}
    15541718
    15551719@media screen and ( max-height: 290px ) {
     
    15661730                height: calc(100% - 92px);
    15671731                padding-bottom: 2px;
    15681732        }
     1733
     1734        #search-panel .query-results {
     1735                position: static;
     1736        }
    15691737}
    15701738
    15711739div.wp-link-preview {
  • src/wp-includes/js/wplink.js

     
    1 
    21var wpLink;
    32
    43( function( $, wpLinkL10n, wp ) {
    5         var editor, correctedURL, linkNode,
     4        var editor, searchTimer, River, Query, correctedURL, linkNode,
     5                emailRegexp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
     6                urlRegexp = /^(https?|ftp):\/\/[A-Z0-9.-]+\.[A-Z]{2,4}[^ "]*$/i,
    67                inputs = {},
     8                rivers = {},
    79                isTouch = ( 'ontouchend' in document );
    810
    911        function getLink() {
     
    1113        }
    1214
    1315        wpLink = {
     16                timeToTriggerRiver: 150,
     17                minRiverAJAXDuration: 200,
     18                riverBottomThreshold: 5,
     19                keySensitivity: 100,
     20                lastSearch: '',
    1421                textarea: '',
    1522
    1623                init: function() {
     
    2330                        // Input
    2431                        inputs.text = $( '#wp-link-text' );
    2532                        inputs.url = $( '#wp-link-url' );
     33                        inputs.nonce = $( '#_ajax_linking_nonce' );
    2634                        inputs.openInNewTab = $( '#wp-link-target' );
     35                        inputs.search = $( '#wp-link-search' );
    2736
    28                         if ( $.ui && $.ui.autocomplete ) {
    29                                 wpLink.setAutocomplete();
    30                         }
     37                        // Build Rivers
     38                        rivers.search = new River( $( '#search-results' ) );
     39                        rivers.recent = new River( $( '#most-recent-results' ) );
     40                        rivers.elements = inputs.dialog.find( '.query-results' );
    3141
    32                         inputs.dialog.on( 'keydown', wpLink.keydown );
    33                         inputs.submit.on( 'click', function( event ) {
     42                        // Get search notice text
     43                        inputs.queryNotice = $( '#query-notice-message' );
     44                        inputs.queryNoticeTextDefault = inputs.queryNotice.find( '.query-notice-default' );
     45                        inputs.queryNoticeTextHint = inputs.queryNotice.find( '.query-notice-hint' );
     46
     47                        // Bind event handlers
     48                        inputs.dialog.keydown( wpLink.keydown );
     49                        inputs.dialog.keyup( wpLink.keyup );
     50                        inputs.submit.click( function( event ) {
    3451                                event.preventDefault();
    3552                                wpLink.update();
    3653                        });
     
    4057                                wpLink.close();
    4158                        });
    4259
     60                        rivers.elements.on( 'river-select', wpLink.updateFields );
     61
     62                        // Display 'hint' message when search field or 'query-results' box are focused
     63                        inputs.search.on( 'focus.wplink', function() {
     64                                inputs.queryNoticeTextDefault.hide();
     65                                inputs.queryNoticeTextHint.removeClass( 'screen-reader-text' ).show();
     66                        } ).on( 'blur.wplink', function() {
     67                                inputs.queryNoticeTextDefault.show();
     68                                inputs.queryNoticeTextHint.addClass( 'screen-reader-text' ).hide();
     69                        } );
     70
     71                        inputs.search.on( 'keyup input', function() {
     72                                window.clearTimeout( searchTimer );
     73                                searchTimer = window.setTimeout( function() {
     74                                        wpLink.searchInternalLinks();
     75                                }, 500 );
     76                        });
     77
    4378                        inputs.url.on( 'paste', function() {
    4479                                setTimeout( wpLink.correctURL, 0 );
    4580                        } );
    46                 },
    4781
    48                 setAutocomplete: function() {
    49                         var $input = inputs.url,
    50                                 cache, last;
    51 
    52                         $input.on( 'keydown', function() {
    53                                 $input.removeAttr( 'aria-activedescendant' );
    54                         } ).autocomplete( {
    55                                 source: function( request, response ) {
    56                                         if ( last === request.term ) {
    57                                                 response( cache );
    58                                                 return;
    59                                         }
    60 
    61                                         if ( /^https?:/.test( request.term ) || request.term.indexOf( '.' ) !== -1 ) {
    62                                                 return response();
    63                                         }
    64 
    65                                         $.post( window.ajaxurl, {
    66                                                 action: 'wp-link-ajax',
    67                                                 page: 1,
    68                                                 search: request.term,
    69                                                 _ajax_linking_nonce: $( '#_ajax_linking_nonce' ).val()
    70                                         }, function( data ) {
    71                                                 cache = data;
    72                                                 response( data );
    73                                         }, 'json' );
    74 
    75                                         last = request.term;
    76                                 },
    77                                 focus: function( event, ui ) {
    78                                         $input.attr( 'aria-activedescendant', 'mce-wp-autocomplete-' + ui.item.ID );
    79                                         /*
    80                                          * Don't empty the URL input field, when using the arrow keys to
    81                                          * highlight items. See api.jqueryui.com/autocomplete/#event-focus
    82                                          */
    83                                         event.preventDefault();
    84                                 },
    85                                 select: function( event, ui ) {
    86                                         $input.val( ui.item.permalink );
    87 
    88                                         if ( inputs.wrap.hasClass( 'has-text-field' ) && $.trim( inputs.text.val() ) === '' ) {
    89                                                 inputs.text.val( ui.item.title );
    90                                         }
    91 
    92                                         // Audible confirmation message when a link has been selected.
    93                                         wp.a11y.speak( wpLinkL10n.linkSelected );
    94 
    95                                         return false;
    96                                 },
    97                                 open: function() {
    98                                         $input.attr( 'aria-expanded', 'true' );
    99                                 },
    100                                 close: function() {
    101                                         $input.attr( 'aria-expanded', 'false' );
    102                                 },
    103                                 minLength: 2,
    104                                 position: {
    105                                         my: 'left top+2'
    106                                 },
    107                                 messages: {
    108                                         noResults: ( typeof window.uiAutocompleteL10n !== 'undefined' ) ? window.uiAutocompleteL10n.noResults : '',
    109                                         results: function( number ) {
    110                                                 if ( typeof window.uiAutocompleteL10n !== 'undefined' ) {
    111                                                         if ( number > 1 ) {
    112                                                                 return window.uiAutocompleteL10n.manyResults.replace( '%d', number );
    113                                                         }
    114 
    115                                                         return window.uiAutocompleteL10n.oneResult;
    116                                                 }
    117                                         }
    118                                 }
    119                         } ).autocomplete( 'instance' )._renderItem = function( ul, item ) {
    120                                 return $( '<li role="option" id="mce-wp-autocomplete-' + item.ID + '">' )
    121                                 .append( '<span class="item-title">' + item.title + '</span>&nbsp;<span class="item-date alignright">' + item.info + '</span>' )
    122                                 .appendTo( ul );
    123                         };
    124 
    125                         $input.attr( {
    126                                 'aria-owns': $input.autocomplete( 'widget' ).attr( 'id' )
    127                         } )
    128                         .on( 'focus', function() {
    129                                 var inputValue = $input.val();
    130                                 /*
    131                                  * Don't trigger a search if the URL field already has a link or is empty.
    132                                  * Also, avoids screen readers announce `No search results`.
    133                                  */
    134                                 if ( inputValue && ! /^https?:/.test( inputValue ) ) {
    135                                         $input.autocomplete( 'search' );
    136                                 }
    137                         } )
    138                         .autocomplete( 'widget' )
    139                                 .addClass( 'wplink-autocomplete' )
    140                                 .attr( 'role', 'listbox' )
    141                                 .removeAttr( 'tabindex' ); // Remove the `tabindex=0` attribute added by jQuery UI.
     82                        inputs.url.on( 'blur', wpLink.correctURL );
    14283                },
    14384
    14485                // If URL wasn't corrected last time and doesn't start with http:, https:, ? # or /, prepend http://
     
    208149                refresh: function( url, text ) {
    209150                        var linkText = '';
    210151
     152                        // Refresh rivers (clear links, check visibility)
     153                        rivers.search.refresh();
     154                        rivers.recent.refresh();
     155
    211156                        if ( wpLink.isMCE() ) {
    212157                                wpLink.mceRefresh( url, text );
    213158                        } else {
     
    237182                                // If this is moved above the selection changes,
    238183                                // IE will show a flashing cursor over the dialog.
    239184                                window.setTimeout( function() {
    240                                         inputs.url.focus()[0].select();
     185                                        inputs.url[0].select();
     186                                        inputs.url.focus();
    241187                                } );
    242188                        }
    243189
     190                        // Load the most recent results if this is the first time opening the panel.
     191                        if ( ! rivers.recent.ul.children().length ) {
     192                                rivers.recent.ajax();
     193                        }
     194
    244195                        correctedURL = inputs.url.val().replace( /^http:\/\//, '' );
    245196                },
    246197
     
    271222                        return true;
    272223                },
    273224
    274                 mceRefresh: function( url, text ) {
    275                         var linkText,
     225                mceRefresh: function( searchStr, text ) {
     226                        var linkText, href,
    276227                                linkNode = getLink(),
    277228                                onlyText = this.hasSelectedText( linkNode );
    278229
    279230                        if ( linkNode ) {
    280231                                linkText = linkNode.innerText || linkNode.textContent;
     232                                href = editor.dom.getAttrib( linkNode, 'href' );
    281233
    282234                                if ( ! $.trim( linkText ) ) {
    283235                                        linkText = text || '';
    284236                                }
    285237
    286                                 url = url || editor.dom.getAttrib( linkNode, 'href' );
     238                                if ( searchStr && ( urlRegexp.test( searchStr ) || emailRegexp.test( searchStr ) ) ) {
     239                                        href = searchStr;
     240                                }
    287241
    288                                 if ( url !== '_wp_link_placeholder' ) {
    289                                         inputs.url.val( url );
     242                                if ( href !== '_wp_link_placeholder' ) {
     243                                        inputs.url.val( href );
    290244                                        inputs.openInNewTab.prop( 'checked', '_blank' === editor.dom.getAttrib( linkNode, 'target' ) );
    291245                                        inputs.submit.val( wpLinkL10n.update );
    292246                                } else {
    293247                                        this.setDefaultValues( linkText );
    294248                                }
     249
     250                                if ( searchStr && searchStr !== href ) {
     251                                        // The user has typed something in the inline dialog. Trigger a search with it.
     252                                        inputs.search.val( searchStr );
     253                                        window.setTimeout( function() {
     254                                                wpLink.searchInternalLinks();
     255                                        } );
     256                                }
    295257                        } else {
    296258                                linkText = editor.selection.getContent({ format: 'text' }) || text || '';
    297259                                this.setDefaultValues( linkText );
     
    424386                        var attrs = wpLink.getAttrs(),
    425387                                link, text;
    426388
    427                         editor.focus();
    428 
    429389                        if ( window.tinymce.isIE && editor.windowManager.wplinkBookmark ) {
    430390                                editor.selection.moveToBookmark( editor.windowManager.wplinkBookmark );
    431391                                editor.windowManager.wplinkBookmark = null;
     
    471431                        wp.a11y.speak( wpLinkL10n.linkInserted );
    472432                },
    473433
    474                 keydown: function( event ) {
    475                         var id;
    476 
    477                         // Escape key.
    478                         if ( 27 === event.keyCode ) {
    479                                 wpLink.close();
    480                                 event.stopImmediatePropagation();
    481                         // Tab key.
    482                         } else if ( 9 === event.keyCode ) {
    483                                 id = event.target.id;
    484 
    485                                 // wp-link-submit must always be the last focusable element in the dialog.
    486                                 // following focusable elements will be skipped on keyboard navigation.
    487                                 if ( id === 'wp-link-submit' && ! event.shiftKey ) {
    488                                         inputs.close.focus();
    489                                         event.preventDefault();
    490                                 } else if ( id === 'wp-link-close' && event.shiftKey ) {
    491                                         inputs.submit.focus();
    492                                         event.preventDefault();
    493                                 }
    494                         }
     434                updateFields: function( e, li ) {
     435                        inputs.url.val( li.children( '.item-permalink' ).val() );
    495436                },
    496437
    497438                getUrlFromSelection: function( selection ) {
    498                         var emailRegexp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
    499                                 urlRegexp = /^(https?|ftp):\/\/[A-Z0-9.-]+\.[A-Z]{2,4}[^ "]*$/i;
    500 
    501439                        if ( ! selection ) {
    502440                                if ( this.isMCE() ) {
    503441                                        selection = editor.selection.getContent({ format: 'text' });
     
    526464
    527465                        // Update save prompt.
    528466                        inputs.submit.val( wpLinkL10n.save );
     467                },
     468
     469                searchInternalLinks: function() {
     470                        var waiting,
     471                                search = inputs.search.val() || '';
     472
     473                        if ( search.length > 2 ) {
     474                                rivers.recent.hide();
     475                                rivers.search.show();
     476
     477                                // Don't search if the keypress didn't change the title.
     478                                if ( wpLink.lastSearch == search )
     479                                        return;
     480
     481                                wpLink.lastSearch = search;
     482                                waiting = inputs.search.parent().find( '.spinner' ).addClass( 'is-active' );
     483
     484                                rivers.search.change( search );
     485                                rivers.search.ajax( function() {
     486                                        waiting.removeClass( 'is-active' );
     487                                });
     488                        } else {
     489                                rivers.search.hide();
     490                                rivers.recent.show();
     491                        }
     492                },
     493
     494                next: function() {
     495                        rivers.search.next();
     496                        rivers.recent.next();
     497                },
     498
     499                prev: function() {
     500                        rivers.search.prev();
     501                        rivers.recent.prev();
     502                },
     503
     504                keydown: function( event ) {
     505                        var fn, id;
     506
     507                        // Escape key.
     508                        if ( 27 === event.keyCode ) {
     509                                wpLink.close();
     510                                event.stopImmediatePropagation();
     511                        // Tab key.
     512                        } else if ( 9 === event.keyCode ) {
     513                                id = event.target.id;
     514
     515                                // wp-link-submit must always be the last focusable element in the dialog.
     516                                // following focusable elements will be skipped on keyboard navigation.
     517                                if ( id === 'wp-link-submit' && ! event.shiftKey ) {
     518                                        inputs.close.focus();
     519                                        event.preventDefault();
     520                                } else if ( id === 'wp-link-close' && event.shiftKey ) {
     521                                        inputs.submit.focus();
     522                                        event.preventDefault();
     523                                }
     524                        }
     525
     526                        // Up Arrow and Down Arrow keys.
     527                        if ( 38 !== event.keyCode && 40 !== event.keyCode ) {
     528                                return;
     529                        }
     530
     531                        if ( document.activeElement &&
     532                                ( document.activeElement.id === 'link-title-field' || document.activeElement.id === 'url-field' ) ) {
     533                                return;
     534                        }
     535
     536                        // Up Arrow key.
     537                        fn = 38 === event.keyCode ? 'prev' : 'next';
     538                        clearInterval( wpLink.keyInterval );
     539                        wpLink[ fn ]();
     540                        wpLink.keyInterval = setInterval( wpLink[ fn ], wpLink.keySensitivity );
     541                        event.preventDefault();
     542                },
     543
     544                keyup: function( event ) {
     545                        // Up Arrow and Down Arrow keys.
     546                        if ( 38 === event.keyCode || 40 === event.keyCode ) {
     547                                clearInterval( wpLink.keyInterval );
     548                                event.preventDefault();
     549                        }
     550                },
     551
     552                delayedCallback: function( func, delay ) {
     553                        var timeoutTriggered, funcTriggered, funcArgs, funcContext;
     554
     555                        if ( ! delay )
     556                                return func;
     557
     558                        setTimeout( function() {
     559                                if ( funcTriggered )
     560                                        return func.apply( funcContext, funcArgs );
     561                                // Otherwise, wait.
     562                                timeoutTriggered = true;
     563                        }, delay );
     564
     565                        return function() {
     566                                if ( timeoutTriggered )
     567                                        return func.apply( this, arguments );
     568                                // Otherwise, wait.
     569                                funcArgs = arguments;
     570                                funcContext = this;
     571                                funcTriggered = true;
     572                        };
    529573                }
    530574        };
    531575
     576        River = function( element, search ) {
     577                var self = this;
     578                this.element = element;
     579                this.ul = element.children( 'ul' );
     580                this.contentHeight = element.children( '#link-selector-height' );
     581                this.waiting = element.find('.river-waiting');
     582
     583                this.change( search );
     584                this.refresh();
     585
     586                $( '#wp-link .query-results, #wp-link #link-selector' ).scroll( function() {
     587                        self.maybeLoad();
     588                });
     589                element.on( 'click', 'li', function( event ) {
     590                        self.select( $( this ), event );
     591                });
     592        };
     593
     594        $.extend( River.prototype, {
     595                refresh: function() {
     596                        this.deselect();
     597                        this.visible = this.element.is( ':visible' );
     598                },
     599                show: function() {
     600                        if ( ! this.visible ) {
     601                                this.deselect();
     602                                this.element.show();
     603                                this.visible = true;
     604                        }
     605                },
     606                hide: function() {
     607                        this.element.hide();
     608                        this.visible = false;
     609                },
     610                // Selects a list item and triggers the river-select event.
     611                select: function( li, event ) {
     612                        var liHeight, elHeight, liTop, elTop;
     613
     614                        if ( li.hasClass( 'unselectable' ) || li == this.selected )
     615                                return;
     616
     617                        this.deselect();
     618                        this.selected = li.addClass( 'selected' );
     619                        // Make sure the element is visible
     620                        liHeight = li.outerHeight();
     621                        elHeight = this.element.height();
     622                        liTop = li.position().top;
     623                        elTop = this.element.scrollTop();
     624
     625                        if ( liTop < 0 ) // Make first visible element
     626                                this.element.scrollTop( elTop + liTop );
     627                        else if ( liTop + liHeight > elHeight ) // Make last visible element
     628                                this.element.scrollTop( elTop + liTop - elHeight + liHeight );
     629
     630                        // Trigger the river-select event
     631                        this.element.trigger( 'river-select', [ li, event, this ] );
     632                },
     633                deselect: function() {
     634                        if ( this.selected )
     635                                this.selected.removeClass( 'selected' );
     636                        this.selected = false;
     637                },
     638                prev: function() {
     639                        if ( ! this.visible )
     640                                return;
     641
     642                        var to;
     643                        if ( this.selected ) {
     644                                to = this.selected.prev( 'li' );
     645                                if ( to.length )
     646                                        this.select( to );
     647                        }
     648                },
     649                next: function() {
     650                        if ( ! this.visible )
     651                                return;
     652
     653                        var to = this.selected ? this.selected.next( 'li' ) : $( 'li:not(.unselectable):first', this.element );
     654                        if ( to.length )
     655                                this.select( to );
     656                },
     657                ajax: function( callback ) {
     658                        var self = this,
     659                                delay = this.query.page == 1 ? 0 : wpLink.minRiverAJAXDuration,
     660                                response = wpLink.delayedCallback( function( results, params ) {
     661                                        self.process( results, params );
     662                                        if ( callback )
     663                                                callback( results, params );
     664                                }, delay );
     665
     666                        this.query.ajax( response );
     667                },
     668                change: function( search ) {
     669                        if ( this.query && this._search == search )
     670                                return;
     671
     672                        this._search = search;
     673                        this.query = new Query( search );
     674                        this.element.scrollTop( 0 );
     675                },
     676                process: function( results, params ) {
     677                        var list = '', alt = true, classes = '',
     678                                firstPage = params.page == 1;
     679
     680                        if ( ! results ) {
     681                                if ( firstPage ) {
     682                                        list += '<li class="unselectable no-matches-found"><span class="item-title"><em>' +
     683                                                wpLinkL10n.noMatchesFound + '</em></span></li>';
     684                                }
     685                        } else {
     686                                $.each( results, function() {
     687                                        classes = alt ? 'alternate' : '';
     688                                        classes += this.title ? '' : ' no-title';
     689                                        list += classes ? '<li class="' + classes + '">' : '<li>';
     690                                        list += '<input type="hidden" class="item-permalink" value="' + this.permalink + '" />';
     691                                        list += '<span class="item-title">';
     692                                        list += this.title ? this.title : wpLinkL10n.noTitle;
     693                                        list += '</span><span class="item-info">' + this.info + '</span></li>';
     694                                        alt = ! alt;
     695                                });
     696                        }
     697
     698                        this.ul[ firstPage ? 'html' : 'append' ]( list );
     699                },
     700                maybeLoad: function() {
     701                        var self = this,
     702                                el = this.element,
     703                                bottom = el.scrollTop() + el.height();
     704
     705                        if ( ! this.query.ready() || bottom < this.contentHeight.height() - wpLink.riverBottomThreshold )
     706                                return;
     707
     708                        setTimeout(function() {
     709                                var newTop = el.scrollTop(),
     710                                        newBottom = newTop + el.height();
     711
     712                                if ( ! self.query.ready() || newBottom < self.contentHeight.height() - wpLink.riverBottomThreshold )
     713                                        return;
     714
     715                                self.waiting.addClass( 'is-active' );
     716                                el.scrollTop( newTop + self.waiting.outerHeight() );
     717
     718                                self.ajax( function() {
     719                                        self.waiting.removeClass( 'is-active' );
     720                                });
     721                        }, wpLink.timeToTriggerRiver );
     722                }
     723        });
     724
     725        Query = function( search ) {
     726                this.page = 1;
     727                this.allLoaded = false;
     728                this.querying = false;
     729                this.search = search;
     730        };
     731
     732        $.extend( Query.prototype, {
     733                ready: function() {
     734                        return ! ( this.querying || this.allLoaded );
     735                },
     736                ajax: function( callback ) {
     737                        var self = this,
     738                                query = {
     739                                        action : 'wp-link-ajax',
     740                                        page : this.page,
     741                                        '_ajax_linking_nonce' : inputs.nonce.val()
     742                                };
     743
     744                        if ( this.search )
     745                                query.search = this.search;
     746
     747                        this.querying = true;
     748
     749                        $.post( window.ajaxurl, query, function( r ) {
     750                                self.page++;
     751                                self.querying = false;
     752                                self.allLoaded = ! r;
     753                                callback( r, query );
     754                        }, 'json' );
     755                }
     756        });
     757
    532758        $( document ).ready( wpLink.init );
    533759})( jQuery, window.wpLinkL10n, window.wp );