|
1 var wpLink; |
|
2 |
|
3 (function($){ |
|
4 var inputs = {}, rivers = {}, ed, River, Query; |
|
5 |
|
6 wpLink = { |
|
7 timeToTriggerRiver: 150, |
|
8 minRiverAJAXDuration: 200, |
|
9 riverBottomThreshold: 5, |
|
10 keySensitivity: 100, |
|
11 lastSearch: '', |
|
12 textarea: '', |
|
13 |
|
14 init : function() { |
|
15 inputs.dialog = $('#wp-link'); |
|
16 inputs.submit = $('#wp-link-submit'); |
|
17 // URL |
|
18 inputs.url = $('#url-field'); |
|
19 inputs.nonce = $('#_ajax_linking_nonce'); |
|
20 // Secondary options |
|
21 inputs.title = $('#link-title-field'); |
|
22 // Advanced Options |
|
23 inputs.openInNewTab = $('#link-target-checkbox'); |
|
24 inputs.search = $('#search-field'); |
|
25 // Build Rivers |
|
26 rivers.search = new River( $('#search-results') ); |
|
27 rivers.recent = new River( $('#most-recent-results') ); |
|
28 rivers.elements = $('.query-results', inputs.dialog); |
|
29 |
|
30 // Bind event handlers |
|
31 inputs.dialog.keydown( wpLink.keydown ); |
|
32 inputs.dialog.keyup( wpLink.keyup ); |
|
33 inputs.submit.click( function(e){ |
|
34 e.preventDefault(); |
|
35 wpLink.update(); |
|
36 }); |
|
37 $('#wp-link-cancel').click( function(e){ |
|
38 e.preventDefault(); |
|
39 wpLink.close(); |
|
40 }); |
|
41 $('#internal-toggle').click( wpLink.toggleInternalLinking ); |
|
42 |
|
43 rivers.elements.bind('river-select', wpLink.updateFields ); |
|
44 |
|
45 inputs.search.keyup( wpLink.searchInternalLinks ); |
|
46 |
|
47 inputs.dialog.bind('wpdialogrefresh', wpLink.refresh); |
|
48 inputs.dialog.bind('wpdialogbeforeopen', wpLink.beforeOpen); |
|
49 inputs.dialog.bind('wpdialogclose', wpLink.onClose); |
|
50 }, |
|
51 |
|
52 beforeOpen : function() { |
|
53 wpLink.range = null; |
|
54 |
|
55 if ( ! wpLink.isMCE() && document.selection ) { |
|
56 wpLink.textarea.focus(); |
|
57 wpLink.range = document.selection.createRange(); |
|
58 } |
|
59 }, |
|
60 |
|
61 open : function() { |
|
62 if ( !wpActiveEditor ) |
|
63 return; |
|
64 |
|
65 this.textarea = $('#'+wpActiveEditor).get(0); |
|
66 |
|
67 // Initialize the dialog if necessary (html mode). |
|
68 if ( ! inputs.dialog.data('wpdialog') ) { |
|
69 inputs.dialog.wpdialog({ |
|
70 title: wpLinkL10n.title, |
|
71 width: 480, |
|
72 height: 'auto', |
|
73 modal: true, |
|
74 dialogClass: 'wp-dialog' |
|
75 }); |
|
76 } |
|
77 |
|
78 inputs.dialog.wpdialog('open'); |
|
79 }, |
|
80 |
|
81 isMCE : function() { |
|
82 return tinyMCEPopup && ( ed = tinyMCEPopup.editor ) && ! ed.isHidden(); |
|
83 }, |
|
84 |
|
85 refresh : function() { |
|
86 // Refresh rivers (clear links, check visibility) |
|
87 rivers.search.refresh(); |
|
88 rivers.recent.refresh(); |
|
89 |
|
90 if ( wpLink.isMCE() ) |
|
91 wpLink.mceRefresh(); |
|
92 else |
|
93 wpLink.setDefaultValues(); |
|
94 |
|
95 // Focus the URL field and highlight its contents. |
|
96 // If this is moved above the selection changes, |
|
97 // IE will show a flashing cursor over the dialog. |
|
98 inputs.url.focus()[0].select(); |
|
99 // Load the most recent results if this is the first time opening the panel. |
|
100 if ( ! rivers.recent.ul.children().length ) |
|
101 rivers.recent.ajax(); |
|
102 }, |
|
103 |
|
104 mceRefresh : function() { |
|
105 var e; |
|
106 ed = tinyMCEPopup.editor; |
|
107 |
|
108 tinyMCEPopup.restoreSelection(); |
|
109 |
|
110 // If link exists, select proper values. |
|
111 if ( e = ed.dom.getParent(ed.selection.getNode(), 'A') ) { |
|
112 // Set URL and description. |
|
113 inputs.url.val( ed.dom.getAttrib(e, 'href') ); |
|
114 inputs.title.val( ed.dom.getAttrib(e, 'title') ); |
|
115 // Set open in new tab. |
|
116 inputs.openInNewTab.prop('checked', ( "_blank" == ed.dom.getAttrib( e, 'target' ) ) ); |
|
117 // Update save prompt. |
|
118 inputs.submit.val( wpLinkL10n.update ); |
|
119 |
|
120 // If there's no link, set the default values. |
|
121 } else { |
|
122 wpLink.setDefaultValues(); |
|
123 } |
|
124 }, |
|
125 |
|
126 close : function() { |
|
127 if ( wpLink.isMCE() ) |
|
128 tinyMCEPopup.close(); |
|
129 else |
|
130 inputs.dialog.wpdialog('close'); |
|
131 }, |
|
132 |
|
133 onClose: function() { |
|
134 if ( ! wpLink.isMCE() ) { |
|
135 wpLink.textarea.focus(); |
|
136 if ( wpLink.range ) { |
|
137 wpLink.range.moveToBookmark( wpLink.range.getBookmark() ); |
|
138 wpLink.range.select(); |
|
139 } |
|
140 } |
|
141 }, |
|
142 |
|
143 getAttrs : function() { |
|
144 return { |
|
145 href : inputs.url.val(), |
|
146 title : inputs.title.val(), |
|
147 target : inputs.openInNewTab.prop('checked') ? '_blank' : '' |
|
148 }; |
|
149 }, |
|
150 |
|
151 update : function() { |
|
152 if ( wpLink.isMCE() ) |
|
153 wpLink.mceUpdate(); |
|
154 else |
|
155 wpLink.htmlUpdate(); |
|
156 }, |
|
157 |
|
158 htmlUpdate : function() { |
|
159 var attrs, html, begin, end, cursor, |
|
160 textarea = wpLink.textarea; |
|
161 |
|
162 if ( ! textarea ) |
|
163 return; |
|
164 |
|
165 attrs = wpLink.getAttrs(); |
|
166 |
|
167 // If there's no href, return. |
|
168 if ( ! attrs.href || attrs.href == 'http://' ) |
|
169 return; |
|
170 |
|
171 // Build HTML |
|
172 html = '<a href="' + attrs.href + '"'; |
|
173 |
|
174 if ( attrs.title ) |
|
175 html += ' title="' + attrs.title + '"'; |
|
176 if ( attrs.target ) |
|
177 html += ' target="' + attrs.target + '"'; |
|
178 |
|
179 html += '>'; |
|
180 |
|
181 // Insert HTML |
|
182 if ( document.selection && wpLink.range ) { |
|
183 // IE |
|
184 // Note: If no text is selected, IE will not place the cursor |
|
185 // inside the closing tag. |
|
186 textarea.focus(); |
|
187 wpLink.range.text = html + wpLink.range.text + '</a>'; |
|
188 wpLink.range.moveToBookmark( wpLink.range.getBookmark() ); |
|
189 wpLink.range.select(); |
|
190 |
|
191 wpLink.range = null; |
|
192 } else if ( typeof textarea.selectionStart !== 'undefined' ) { |
|
193 // W3C |
|
194 begin = textarea.selectionStart; |
|
195 end = textarea.selectionEnd; |
|
196 selection = textarea.value.substring( begin, end ); |
|
197 html = html + selection + '</a>'; |
|
198 cursor = begin + html.length; |
|
199 |
|
200 // If no next is selected, place the cursor inside the closing tag. |
|
201 if ( begin == end ) |
|
202 cursor -= '</a>'.length; |
|
203 |
|
204 textarea.value = textarea.value.substring( 0, begin ) |
|
205 + html |
|
206 + textarea.value.substring( end, textarea.value.length ); |
|
207 |
|
208 // Update cursor position |
|
209 textarea.selectionStart = textarea.selectionEnd = cursor; |
|
210 } |
|
211 |
|
212 wpLink.close(); |
|
213 textarea.focus(); |
|
214 }, |
|
215 |
|
216 mceUpdate : function() { |
|
217 var ed = tinyMCEPopup.editor, |
|
218 attrs = wpLink.getAttrs(), |
|
219 e, b; |
|
220 |
|
221 tinyMCEPopup.restoreSelection(); |
|
222 e = ed.dom.getParent(ed.selection.getNode(), 'A'); |
|
223 |
|
224 // If the values are empty, unlink and return |
|
225 if ( ! attrs.href || attrs.href == 'http://' ) { |
|
226 if ( e ) { |
|
227 b = ed.selection.getBookmark(); |
|
228 ed.dom.remove(e, 1); |
|
229 ed.selection.moveToBookmark(b); |
|
230 tinyMCEPopup.execCommand("mceEndUndoLevel"); |
|
231 wpLink.close(); |
|
232 } |
|
233 return; |
|
234 } |
|
235 |
|
236 if (e == null) { |
|
237 ed.getDoc().execCommand("unlink", false, null); |
|
238 tinyMCEPopup.execCommand("mceInsertLink", false, "#mce_temp_url#", {skip_undo : 1}); |
|
239 |
|
240 tinymce.each(ed.dom.select("a"), function(n) { |
|
241 if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') { |
|
242 e = n; |
|
243 ed.dom.setAttribs(e, attrs); |
|
244 } |
|
245 }); |
|
246 |
|
247 // Sometimes WebKit lets a user create a link where |
|
248 // they shouldn't be able to. In this case, CreateLink |
|
249 // injects "#mce_temp_url#" into their content. Fix it. |
|
250 if ( tinymce.isWebKit && $(e).text() == '#mce_temp_url#' ) { |
|
251 ed.dom.remove(e); |
|
252 e = null; |
|
253 } |
|
254 } else { |
|
255 ed.dom.setAttribs(e, attrs); |
|
256 } |
|
257 |
|
258 // Move the caret if selection was not an image. |
|
259 if ( e && (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') ) { |
|
260 ed.selection.select(e); |
|
261 ed.selection.collapse(0); |
|
262 tinyMCEPopup.storeSelection(); |
|
263 } |
|
264 |
|
265 ed.execCommand("mceEndUndoLevel"); |
|
266 wpLink.close(); |
|
267 ed.focus(); |
|
268 }, |
|
269 |
|
270 updateFields : function( e, li, originalEvent ) { |
|
271 inputs.url.val( li.children('.item-permalink').val() ); |
|
272 inputs.title.val( li.hasClass('no-title') ? '' : li.children('.item-title').text() ); |
|
273 if ( originalEvent && originalEvent.type == "click" ) |
|
274 inputs.url.focus(); |
|
275 }, |
|
276 setDefaultValues : function() { |
|
277 // Set URL and description to defaults. |
|
278 // Leave the new tab setting as-is. |
|
279 inputs.url.val('http://'); |
|
280 inputs.title.val(''); |
|
281 |
|
282 // Update save prompt. |
|
283 inputs.submit.val( wpLinkL10n.save ); |
|
284 }, |
|
285 |
|
286 searchInternalLinks : function() { |
|
287 var t = $(this), waiting, |
|
288 search = t.val(); |
|
289 |
|
290 if ( search.length > 2 ) { |
|
291 rivers.recent.hide(); |
|
292 rivers.search.show(); |
|
293 |
|
294 // Don't search if the keypress didn't change the title. |
|
295 if ( wpLink.lastSearch == search ) |
|
296 return; |
|
297 |
|
298 wpLink.lastSearch = search; |
|
299 waiting = t.parent().find('.spinner').show(); |
|
300 |
|
301 rivers.search.change( search ); |
|
302 rivers.search.ajax( function(){ waiting.hide(); }); |
|
303 } else { |
|
304 rivers.search.hide(); |
|
305 rivers.recent.show(); |
|
306 } |
|
307 }, |
|
308 |
|
309 next : function() { |
|
310 rivers.search.next(); |
|
311 rivers.recent.next(); |
|
312 }, |
|
313 prev : function() { |
|
314 rivers.search.prev(); |
|
315 rivers.recent.prev(); |
|
316 }, |
|
317 |
|
318 keydown : function( event ) { |
|
319 var fn, key = $.ui.keyCode; |
|
320 |
|
321 switch( event.which ) { |
|
322 case key.UP: |
|
323 fn = 'prev'; |
|
324 case key.DOWN: |
|
325 fn = fn || 'next'; |
|
326 clearInterval( wpLink.keyInterval ); |
|
327 wpLink[ fn ](); |
|
328 wpLink.keyInterval = setInterval( wpLink[ fn ], wpLink.keySensitivity ); |
|
329 break; |
|
330 default: |
|
331 return; |
|
332 } |
|
333 event.preventDefault(); |
|
334 }, |
|
335 keyup: function( event ) { |
|
336 var key = $.ui.keyCode; |
|
337 |
|
338 switch( event.which ) { |
|
339 case key.ESCAPE: |
|
340 event.stopImmediatePropagation(); |
|
341 if ( ! $(document).triggerHandler( 'wp_CloseOnEscape', [{ event: event, what: 'wplink', cb: wpLink.close }] ) ) |
|
342 wpLink.close(); |
|
343 |
|
344 return false; |
|
345 break; |
|
346 case key.UP: |
|
347 case key.DOWN: |
|
348 clearInterval( wpLink.keyInterval ); |
|
349 break; |
|
350 default: |
|
351 return; |
|
352 } |
|
353 event.preventDefault(); |
|
354 }, |
|
355 |
|
356 delayedCallback : function( func, delay ) { |
|
357 var timeoutTriggered, funcTriggered, funcArgs, funcContext; |
|
358 |
|
359 if ( ! delay ) |
|
360 return func; |
|
361 |
|
362 setTimeout( function() { |
|
363 if ( funcTriggered ) |
|
364 return func.apply( funcContext, funcArgs ); |
|
365 // Otherwise, wait. |
|
366 timeoutTriggered = true; |
|
367 }, delay); |
|
368 |
|
369 return function() { |
|
370 if ( timeoutTriggered ) |
|
371 return func.apply( this, arguments ); |
|
372 // Otherwise, wait. |
|
373 funcArgs = arguments; |
|
374 funcContext = this; |
|
375 funcTriggered = true; |
|
376 }; |
|
377 }, |
|
378 |
|
379 toggleInternalLinking : function( event ) { |
|
380 var panel = $('#search-panel'), |
|
381 widget = inputs.dialog.wpdialog('widget'), |
|
382 // We're about to toggle visibility; it's currently the opposite |
|
383 visible = !panel.is(':visible'), |
|
384 win = $(window); |
|
385 |
|
386 $(this).toggleClass('toggle-arrow-active', visible); |
|
387 |
|
388 inputs.dialog.height('auto'); |
|
389 panel.slideToggle( 300, function() { |
|
390 setUserSetting('wplink', visible ? '1' : '0'); |
|
391 inputs[ visible ? 'search' : 'url' ].focus(); |
|
392 |
|
393 // Move the box if the box is now expanded, was opened in a collapsed state, |
|
394 // and if it needs to be moved. (Judged by bottom not being positive or |
|
395 // bottom being smaller than top.) |
|
396 var scroll = win.scrollTop(), |
|
397 top = widget.offset().top, |
|
398 bottom = top + widget.outerHeight(), |
|
399 diff = bottom - win.height(); |
|
400 |
|
401 if ( diff > scroll ) { |
|
402 widget.animate({'top': diff < top ? top - diff : scroll }, 200); |
|
403 } |
|
404 }); |
|
405 event.preventDefault(); |
|
406 } |
|
407 } |
|
408 |
|
409 River = function( element, search ) { |
|
410 var self = this; |
|
411 this.element = element; |
|
412 this.ul = element.children('ul'); |
|
413 this.waiting = element.find('.river-waiting'); |
|
414 |
|
415 this.change( search ); |
|
416 this.refresh(); |
|
417 |
|
418 element.scroll( function(){ self.maybeLoad(); }); |
|
419 element.delegate('li', 'click', function(e){ self.select( $(this), e ); }); |
|
420 }; |
|
421 |
|
422 $.extend( River.prototype, { |
|
423 refresh: function() { |
|
424 this.deselect(); |
|
425 this.visible = this.element.is(':visible'); |
|
426 }, |
|
427 show: function() { |
|
428 if ( ! this.visible ) { |
|
429 this.deselect(); |
|
430 this.element.show(); |
|
431 this.visible = true; |
|
432 } |
|
433 }, |
|
434 hide: function() { |
|
435 this.element.hide(); |
|
436 this.visible = false; |
|
437 }, |
|
438 // Selects a list item and triggers the river-select event. |
|
439 select: function( li, event ) { |
|
440 var liHeight, elHeight, liTop, elTop; |
|
441 |
|
442 if ( li.hasClass('unselectable') || li == this.selected ) |
|
443 return; |
|
444 |
|
445 this.deselect(); |
|
446 this.selected = li.addClass('selected'); |
|
447 // Make sure the element is visible |
|
448 liHeight = li.outerHeight(); |
|
449 elHeight = this.element.height(); |
|
450 liTop = li.position().top; |
|
451 elTop = this.element.scrollTop(); |
|
452 |
|
453 if ( liTop < 0 ) // Make first visible element |
|
454 this.element.scrollTop( elTop + liTop ); |
|
455 else if ( liTop + liHeight > elHeight ) // Make last visible element |
|
456 this.element.scrollTop( elTop + liTop - elHeight + liHeight ); |
|
457 |
|
458 // Trigger the river-select event |
|
459 this.element.trigger('river-select', [ li, event, this ]); |
|
460 }, |
|
461 deselect: function() { |
|
462 if ( this.selected ) |
|
463 this.selected.removeClass('selected'); |
|
464 this.selected = false; |
|
465 }, |
|
466 prev: function() { |
|
467 if ( ! this.visible ) |
|
468 return; |
|
469 |
|
470 var to; |
|
471 if ( this.selected ) { |
|
472 to = this.selected.prev('li'); |
|
473 if ( to.length ) |
|
474 this.select( to ); |
|
475 } |
|
476 }, |
|
477 next: function() { |
|
478 if ( ! this.visible ) |
|
479 return; |
|
480 |
|
481 var to = this.selected ? this.selected.next('li') : $('li:not(.unselectable):first', this.element); |
|
482 if ( to.length ) |
|
483 this.select( to ); |
|
484 }, |
|
485 ajax: function( callback ) { |
|
486 var self = this, |
|
487 delay = this.query.page == 1 ? 0 : wpLink.minRiverAJAXDuration, |
|
488 response = wpLink.delayedCallback( function( results, params ) { |
|
489 self.process( results, params ); |
|
490 if ( callback ) |
|
491 callback( results, params ); |
|
492 }, delay ); |
|
493 |
|
494 this.query.ajax( response ); |
|
495 }, |
|
496 change: function( search ) { |
|
497 if ( this.query && this._search == search ) |
|
498 return; |
|
499 |
|
500 this._search = search; |
|
501 this.query = new Query( search ); |
|
502 this.element.scrollTop(0); |
|
503 }, |
|
504 process: function( results, params ) { |
|
505 var list = '', alt = true, classes = '', |
|
506 firstPage = params.page == 1; |
|
507 |
|
508 if ( !results ) { |
|
509 if ( firstPage ) { |
|
510 list += '<li class="unselectable"><span class="item-title"><em>' |
|
511 + wpLinkL10n.noMatchesFound |
|
512 + '</em></span></li>'; |
|
513 } |
|
514 } else { |
|
515 $.each( results, function() { |
|
516 classes = alt ? 'alternate' : ''; |
|
517 classes += this['title'] ? '' : ' no-title'; |
|
518 list += classes ? '<li class="' + classes + '">' : '<li>'; |
|
519 list += '<input type="hidden" class="item-permalink" value="' + this['permalink'] + '" />'; |
|
520 list += '<span class="item-title">'; |
|
521 list += this['title'] ? this['title'] : wpLinkL10n.noTitle; |
|
522 list += '</span><span class="item-info">' + this['info'] + '</span></li>'; |
|
523 alt = ! alt; |
|
524 }); |
|
525 } |
|
526 |
|
527 this.ul[ firstPage ? 'html' : 'append' ]( list ); |
|
528 }, |
|
529 maybeLoad: function() { |
|
530 var self = this, |
|
531 el = this.element, |
|
532 bottom = el.scrollTop() + el.height(); |
|
533 |
|
534 if ( ! this.query.ready() || bottom < this.ul.height() - wpLink.riverBottomThreshold ) |
|
535 return; |
|
536 |
|
537 setTimeout(function() { |
|
538 var newTop = el.scrollTop(), |
|
539 newBottom = newTop + el.height(); |
|
540 |
|
541 if ( ! self.query.ready() || newBottom < self.ul.height() - wpLink.riverBottomThreshold ) |
|
542 return; |
|
543 |
|
544 self.waiting.show(); |
|
545 el.scrollTop( newTop + self.waiting.outerHeight() ); |
|
546 |
|
547 self.ajax( function() { self.waiting.hide(); }); |
|
548 }, wpLink.timeToTriggerRiver ); |
|
549 } |
|
550 }); |
|
551 |
|
552 Query = function( search ) { |
|
553 this.page = 1; |
|
554 this.allLoaded = false; |
|
555 this.querying = false; |
|
556 this.search = search; |
|
557 }; |
|
558 |
|
559 $.extend( Query.prototype, { |
|
560 ready: function() { |
|
561 return !( this.querying || this.allLoaded ); |
|
562 }, |
|
563 ajax: function( callback ) { |
|
564 var self = this, |
|
565 query = { |
|
566 action : 'wp-link-ajax', |
|
567 page : this.page, |
|
568 '_ajax_linking_nonce' : inputs.nonce.val() |
|
569 }; |
|
570 |
|
571 if ( this.search ) |
|
572 query.search = this.search; |
|
573 |
|
574 this.querying = true; |
|
575 |
|
576 $.post( ajaxurl, query, function(r) { |
|
577 self.page++; |
|
578 self.querying = false; |
|
579 self.allLoaded = !r; |
|
580 callback( r, query ); |
|
581 }, "json" ); |
|
582 } |
|
583 }); |
|
584 |
|
585 $(document).ready( wpLink.init ); |
|
586 })(jQuery); |