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