4 var inputs = {}, rivers = {}, ed, River, Query;
7 timeToTriggerRiver: 150,
8 minRiverAJAXDuration: 200,
9 riverBottomThreshold: 5,
13 inputs.dialog = $('#wp-link');
14 inputs.submit = $('#wp-link-submit');
16 inputs.url = $('#url-field');
18 inputs.title = $('#link-title-field');
20 inputs.openInNewTab = $('#link-target-checkbox');
21 inputs.search = $('#search-field');
23 rivers.search = new River( $('#search-results') );
24 rivers.recent = new River( $('#most-recent-results') );
25 rivers.elements = $('.query-results', inputs.dialog);
27 // Bind event handlers
28 inputs.dialog.keydown( wpLink.keydown );
29 inputs.dialog.keyup( wpLink.keyup );
30 inputs.submit.click( function(e){
34 $('#wp-link-cancel').click( wpLink.cancel );
35 $('#internal-toggle').click( wpLink.toggleInternalLinking );
37 rivers.elements.bind('river-select', wpLink.updateFields );
39 inputs.search.keyup( wpLink.searchInternalLinks );
41 inputs.dialog.bind('wpdialogrefresh', wpLink.refresh);
44 refresh : function() {
46 ed = tinyMCEPopup.editor;
48 // Refresh rivers (clear links, check visibility)
49 rivers.search.refresh();
50 rivers.recent.refresh();
52 tinyMCEPopup.restoreSelection();
54 // If link exists, select proper values.
55 if ( e = ed.dom.getParent(ed.selection.getNode(), 'A') ) {
56 // Set URL and description.
57 inputs.url.val( e.href );
58 inputs.title.val( ed.dom.getAttrib(e, 'title') );
59 // Set open in new tab.
60 if ( "_blank" == ed.dom.getAttrib(e, 'target') )
61 inputs.openInNewTab.attr('checked','checked');
62 // Update save prompt.
63 inputs.submit.val( wpLinkL10n.update );
65 // If there's no link, set the default values.
67 wpLink.setDefaultValues();
68 // Update save prompt.
69 inputs.submit.val( wpLinkL10n.save );
72 tinyMCEPopup.storeSelection();
73 // Focus the URL field and highlight its contents.
74 // If this is moved above the selection changes,
75 // IE will show a flashing cursor over the dialog.
76 inputs.url.focus()[0].select();
77 // Load the most recent results if this is the first time opening the panel.
78 if ( ! rivers.recent.ul.children().length )
87 var ed = tinyMCEPopup.editor,
89 href : inputs.url.val(),
90 title : inputs.title.val(),
91 target : inputs.openInNewTab.attr('checked') ? '_blank' : ''
94 tinyMCEPopup.restoreSelection();
95 e = ed.dom.getParent(ed.selection.getNode(), 'A');
97 // If the values are empty, unlink and return
98 if ( ! attrs.href || attrs.href == 'http://' ) {
100 tinyMCEPopup.execCommand("mceBeginUndoLevel");
101 b = ed.selection.getBookmark();
103 ed.selection.moveToBookmark(b);
104 tinyMCEPopup.execCommand("mceEndUndoLevel");
105 tinyMCEPopup.close();
110 tinyMCEPopup.execCommand("mceBeginUndoLevel");
113 ed.getDoc().execCommand("unlink", false, null);
114 tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1});
116 tinymce.each(ed.dom.select("a"), function(n) {
117 if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') {
119 ed.dom.setAttribs(e, attrs);
123 // Sometimes WebKit lets a user create a link where
124 // they shouldn't be able to. In this case, CreateLink
125 // injects "#mce_temp_url#" into their content. Fix it.
126 if ( $(e).text() == '#mce_temp_url#' ) {
131 ed.dom.setAttribs(e, attrs);
134 // Don't move caret if selection was image
135 if ( e && (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') ) {
137 ed.selection.select(e);
138 ed.selection.collapse(0);
139 tinyMCEPopup.storeSelection();
142 tinyMCEPopup.execCommand("mceEndUndoLevel");
143 tinyMCEPopup.close();
146 updateFields : function( e, li, originalEvent ) {
147 inputs.url.val( li.children('.item-permalink').val() );
148 inputs.title.val( li.hasClass('no-title') ? '' : li.children('.item-title').text() );
149 if ( originalEvent && originalEvent.type == "click" )
152 setDefaultValues : function() {
153 // Set URL and description to defaults.
154 // Leave the new tab setting as-is.
155 inputs.url.val('http://');
156 inputs.title.val('');
159 searchInternalLinks : function() {
160 var t = $(this), waiting,
163 if ( search.length > 2 ) {
164 rivers.recent.hide();
165 rivers.search.show();
167 // Don't search if the keypress didn't change the title.
168 if ( wpLink.lastSearch == search )
171 wpLink.lastSearch = search;
172 waiting = t.siblings('img.waiting').show();
174 rivers.search.change( search );
175 rivers.search.ajax( function(){ waiting.hide(); });
177 rivers.search.hide();
178 rivers.recent.show();
183 rivers.search.next();
184 rivers.recent.next();
187 rivers.search.prev();
188 rivers.recent.prev();
191 keydown : function( event ) {
192 var fn, key = $.ui.keyCode;
194 switch( event.which ) {
199 clearInterval( wpLink.keyInterval );
201 wpLink.keyInterval = setInterval( wpLink[ fn ], wpLink.keySensitivity );
206 event.preventDefault();
208 keyup: function( event ) {
209 var key = $.ui.keyCode;
211 switch( event.which ) {
217 clearInterval( wpLink.keyInterval );
222 event.preventDefault();
225 delayedCallback : function( func, delay ) {
226 var timeoutTriggered, funcTriggered, funcArgs, funcContext;
231 setTimeout( function() {
233 return func.apply( funcContext, funcArgs );
235 timeoutTriggered = true;
239 if ( timeoutTriggered )
240 return func.apply( this, arguments );
242 funcArgs = arguments;
244 funcTriggered = true;
248 toggleInternalLinking : function( event ) {
249 var panel = $('#search-panel'),
250 widget = inputs.dialog.wpdialog('widget'),
251 // We're about to toggle visibility; it's currently the opposite
252 visible = !panel.is(':visible'),
255 $(this).toggleClass('toggle-arrow-active', visible);
257 inputs.dialog.height('auto');
258 panel.slideToggle( 300, function() {
259 setUserSetting('wplink', visible ? '1' : '0');
260 inputs[ visible ? 'search' : 'url' ].focus();
262 // Move the box if the box is now expanded, was opened in a collapsed state,
263 // and if it needs to be moved. (Judged by bottom not being positive or
264 // bottom being smaller than top.)
265 var scroll = win.scrollTop(),
266 top = widget.offset().top,
267 bottom = top + widget.outerHeight(),
268 diff = bottom - win.height();
270 if ( diff > scroll ) {
271 widget.animate({'top': diff < top ? top - diff : scroll }, 200);
274 event.preventDefault();
278 River = function( element, search ) {
280 this.element = element;
281 this.ul = element.children('ul');
282 this.waiting = element.find('.river-waiting');
284 this.change( search );
287 element.scroll( function(){ self.maybeLoad(); });
288 element.delegate('li', 'click', function(e){ self.select( $(this), e ); });
291 $.extend( River.prototype, {
292 refresh: function() {
294 this.visible = this.element.is(':visible');
297 if ( ! this.visible ) {
305 this.visible = false;
307 // Selects a list item and triggers the river-select event.
308 select: function( li, event ) {
309 var liHeight, elHeight, liTop, elTop;
311 if ( li.hasClass('unselectable') || li == this.selected )
315 this.selected = li.addClass('selected');
316 // Make sure the element is visible
317 liHeight = li.outerHeight();
318 elHeight = this.element.height();
319 liTop = li.position().top;
320 elTop = this.element.scrollTop();
322 if ( liTop < 0 ) // Make first visible element
323 this.element.scrollTop( elTop + liTop );
324 else if ( liTop + liHeight > elHeight ) // Make last visible element
325 this.element.scrollTop( elTop + liTop - elHeight + liHeight );
327 // Trigger the river-select event
328 this.element.trigger('river-select', [ li, event, this ]);
330 deselect: function() {
332 this.selected.removeClass('selected');
333 this.selected = false;
336 if ( ! this.visible )
340 if ( this.selected ) {
341 to = this.selected.prev('li');
347 if ( ! this.visible )
350 var to = this.selected ? this.selected.next('li') : $('li:not(.unselectable):first', this.element);
354 ajax: function( callback ) {
356 delay = this.query.page == 1 ? 0 : wpLink.minRiverAJAXDuration,
357 response = wpLink.delayedCallback( function( results, params ) {
358 self.process( results, params );
360 callback( results, params );
363 this.query.ajax( response );
365 change: function( search ) {
366 if ( this.query && this._search == search )
369 this._search = search;
370 this.query = new Query( search );
371 this.element.scrollTop(0);
373 process: function( results, params ) {
374 var list = '', alt = true, classes = '',
375 firstPage = params.page == 1;
379 list += '<li class="unselectable"><span class="item-title"><em>'
380 + wpLinkL10n.noMatchesFound
381 + '</em></span></li>';
384 $.each( results, function() {
385 classes = alt ? 'alternate' : '';
386 classes += this['title'] ? '' : ' no-title';
387 list += classes ? '<li class="' + classes + '">' : '<li>';
388 list += '<input type="hidden" class="item-permalink" value="' + this['permalink'] + '" />';
389 list += '<span class="item-title">';
390 list += this['title'] ? this['title'] : wpLinkL10n.noTitle;
391 list += '</span><span class="item-info">' + this['info'] + '</span></li>';
396 this.ul[ firstPage ? 'html' : 'append' ]( list );
398 maybeLoad: function() {
401 bottom = el.scrollTop() + el.height();
403 if ( ! this.query.ready() || bottom < this.ul.height() - wpLink.riverBottomThreshold )
406 setTimeout(function() {
407 var newTop = el.scrollTop(),
408 newBottom = newTop + el.height();
410 if ( ! self.query.ready() || newBottom < self.ul.height() - wpLink.riverBottomThreshold )
414 el.scrollTop( newTop + self.waiting.outerHeight() );
416 self.ajax( function() { self.waiting.hide(); });
417 }, wpLink.timeToTriggerRiver );
421 Query = function( search ) {
423 this.allLoaded = false;
424 this.querying = false;
425 this.search = search;
428 $.extend( Query.prototype, {
430 return !( this.querying || this.allLoaded );
432 ajax: function( callback ) {
435 action : 'wp-link-ajax',
437 '_ajax_linking_nonce' : $('#_ajax_linking_nonce').val()
441 query.search = this.search;
443 this.querying = true;
445 $.post( ajaxurl, query, function(r) {
447 self.querying = false;
449 callback( r, query );
454 $(document).ready( wpLink.init );