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