PluginProbe
Friends / 4.3.1
Friends v4.3.1
4.3.2 4.3.1 4.3.0 4.2.2 4.2.1 4.2.0 4.1.0 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.1 All 88 releases
friends / templates / twitter / twitter.js

twitter.js in Friends 4.3.1, at templates/twitter/twitter.js

502 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Twitter theme JS for the Friends plugin.
3 *
4 * Keyboard shortcuts (compact mode):
5 * j/k - Select next/previous item (expands it)
6 * n/p - Select next/previous item without opening
7 * space - Page down, or next item if at bottom of current
8 * shift-space - Page up
9 * o/enter - Toggle open/close current item
10 * s - Toggle star reaction
11 * v - Open original in new tab
12 * cmd-k/ctrl-k - Focus search
13 * ? - Show keyboard shortcuts help
14 */
15 ( function( $ ) {
16 var currentIndex = -1;
17
18 function getItems() {
19 return $( 'section.posts article.card' );
20 }
21
22 function isCompactMode() {
23 return $( 'section.posts' ).hasClass( 'all-collapsed' );
24 }
25
26 function scrollToItem( $item ) {
27 if ( ! $item.length ) {
28 return;
29 }
30 $( 'html, body' ).animate( {
31 scrollTop: $item.offset().top - 80
32 }, 150 );
33 }
34
35 function expandItem( $item ) {
36 if ( ! $item.hasClass( 'uncollapsed' ) ) {
37 $item.addClass( 'uncollapsed' );
38 }
39 }
40
41 function collapseItem( $item ) {
42 $item.removeClass( 'uncollapsed' );
43 }
44
45 function collapseAll() {
46 getItems().removeClass( 'uncollapsed' );
47 }
48
49 function selectItem( index, expand ) {
50 var items = getItems();
51 if ( index < 0 || index >= items.length ) {
52 return;
53 }
54
55 if ( expand ) {
56 collapseAll();
57 }
58
59 currentIndex = index;
60 var $item = items.eq( index );
61
62 items.removeClass( 'mast-current' );
63 $item.addClass( 'mast-current' );
64 scrollToItem( $item );
65
66 if ( expand ) {
67 expandItem( $item );
68 }
69 }
70
71 function isItemBottomVisible( $item ) {
72 var itemBottom = $item.offset().top + $item.outerHeight();
73 var viewBottom = $( window ).scrollTop() + $( window ).height();
74 return itemBottom <= viewBottom;
75 }
76
77 // Wrap multiple images in a gallery grid.
78 document.querySelectorAll( '.posts .card-body' ).forEach( function( body ) {
79 var images = body.querySelectorAll( ':scope > .wp-block-image, :scope > p > img' );
80 if ( images.length < 2 ) {
81 return;
82 }
83
84 var gallery = document.createElement( 'div' );
85 gallery.className = 'friends-gallery gallery-' + Math.min( images.length, 4 );
86
87 var imgElements = [];
88 images.forEach( function( el ) {
89 var img = el.tagName === 'IMG' ? el : el.querySelector( 'img' );
90 if ( img ) {
91 imgElements.push( { img: img, wrapper: el.tagName === 'IMG' ? el.parentNode : el } );
92 }
93 } );
94
95 if ( imgElements.length < 2 ) {
96 return;
97 }
98
99 // Insert gallery before the first image wrapper.
100 imgElements[0].wrapper.parentNode.insertBefore( gallery, imgElements[0].wrapper );
101
102 imgElements.forEach( function( item ) {
103 gallery.appendChild( item.img.cloneNode( true ) );
104 if ( item.wrapper.parentNode ) {
105 item.wrapper.parentNode.removeChild( item.wrapper );
106 }
107 } );
108 } );
109
110 // Toggle compact/expanded mode.
111 $( document ).on( 'click', 'a.toggle-compact', function() {
112 $( 'section.posts' ).toggleClass( 'all-collapsed' );
113 if ( $( 'section.posts' ).is( '.all-collapsed' ) ) {
114 $( 'a.toggle-compact' ).text( friends.text_expanded_mode );
115 getItems().removeClass( 'uncollapsed' );
116 } else {
117 $( 'a.toggle-compact' ).text( friends.text_compact_mode );
118 }
119 return false;
120 } );
121
122 function toggleCard( card ) {
123 var items = getItems();
124 var index = items.index( card );
125 var $card = $( card );
126
127 currentIndex = index;
128 items.removeClass( 'mast-current' );
129 $card.addClass( 'mast-current' );
130
131 items.not( card ).removeClass( 'uncollapsed' );
132 $card.toggleClass( 'uncollapsed' );
133 }
134
135 // Block link clicks on collapsed items.
136 $( document ).on( 'click', 'section.posts.all-collapsed article.card:not(.uncollapsed) .post-meta a, section.posts.all-collapsed article.card:not(.uncollapsed) .card-title a', function( e ) {
137 e.preventDefault();
138 e.stopPropagation();
139 toggleCard( $( this ).closest( 'article.card' )[0] );
140 return false;
141 } );
142
143 // Accordion click.
144 $( document ).on( 'click', 'section.posts.all-collapsed article.card', function( e ) {
145 if ( $( e.target ).closest( 'a, button, input, textarea, form, label, .friends-dropdown' ).length ) {
146 return;
147 }
148
149 toggleCard( this );
150
151 e.stopPropagation();
152 return false;
153 } );
154
155 // Keyboard shortcuts.
156 $( document ).on( 'keydown', function( e ) {
157 if ( ( e.metaKey || e.ctrlKey ) && 75 === e.keyCode ) {
158 e.preventDefault();
159 $( '.twitter-search-input' ).first().focus();
160 return false;
161 }
162
163 if ( 27 === e.keyCode && $( e.target ).is( '.twitter-search-input' ) ) {
164 $( e.target ).blur();
165 return false;
166 }
167
168 if ( $( e.target ).is( 'input, textarea, select, [contenteditable]' ) ) {
169 return;
170 }
171
172 var items = getItems();
173 if ( ! items.length && e.key !== '?' ) {
174 return;
175 }
176
177 if ( ! isCompactMode() && e.key !== '?' ) {
178 return;
179 }
180
181 switch ( e.key ) {
182 case 'j':
183 e.preventDefault();
184 selectItem( currentIndex + 1, true );
185 break;
186
187 case 'k':
188 e.preventDefault();
189 if ( currentIndex > 0 ) {
190 selectItem( currentIndex - 1, true );
191 }
192 break;
193
194 case 'n':
195 e.preventDefault();
196 selectItem( currentIndex + 1, false );
197 break;
198
199 case 'p':
200 e.preventDefault();
201 if ( currentIndex > 0 ) {
202 selectItem( currentIndex - 1, false );
203 }
204 break;
205
206 case ' ':
207 e.preventDefault();
208 if ( e.shiftKey ) {
209 window.scrollBy( 0, -$( window ).height() * 0.8 );
210 } else if ( currentIndex >= 0 && items.eq( currentIndex ).hasClass( 'uncollapsed' ) && ! isItemBottomVisible( items.eq( currentIndex ) ) ) {
211 window.scrollBy( 0, $( window ).height() * 0.8 );
212 } else {
213 selectItem( currentIndex + 1, true );
214 }
215 break;
216
217 case 'o':
218 case 'Enter':
219 if ( currentIndex >= 0 ) {
220 e.preventDefault();
221 var $item = items.eq( currentIndex );
222 if ( $item.hasClass( 'uncollapsed' ) ) {
223 collapseItem( $item );
224 } else {
225 collapseAll();
226 expandItem( $item );
227 }
228 }
229 break;
230
231 case 's':
232 if ( currentIndex >= 0 ) {
233 e.preventDefault();
234 var $star = items.eq( currentIndex ).find( 'button.friends-reaction[data-emoji="2b50"], button.friends-reaction-picker[data-emoji="2b50"]' ).first();
235 if ( $star.length ) {
236 $star.trigger( 'click' );
237 }
238 }
239 break;
240
241 case 'v':
242 if ( currentIndex >= 0 ) {
243 e.preventDefault();
244 var $link = items.eq( currentIndex ).find( '.card-title a, h4 a' ).first();
245 if ( $link.length ) {
246 window.open( $link.attr( 'href' ), '_blank' );
247 }
248 }
249 break;
250
251 case '?':
252 e.preventDefault();
253 var $help = $( '#twitter-shortcuts-help' );
254 if ( $help.length ) {
255 $help.toggle();
256 } else {
257 $( 'body' ).append(
258 '<div id="twitter-shortcuts-help" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:light-dark(#fff,#16181c);border:none;border-radius:16px;padding:24px;z-index:10000;max-width:500px;font-size:15px;box-shadow:light-dark(rgba(101,119,134,.2),rgba(255,255,255,.2)) 0 0 15px,light-dark(rgba(101,119,134,.15),rgba(255,255,255,.1)) 0 0 3px 1px">' +
259 '<h3 style="margin:0 0 16px;font-size:20px;font-weight:800">Keyboard Shortcuts</h3>' +
260 '<table style="width:100%;border-collapse:collapse">' +
261 '<tr><td style="padding:4px 16px 4px 0"><b>j / k</b></td><td>Next / previous item (expand)</td></tr>' +
262 '<tr><td style="padding:4px 16px 4px 0"><b>n / p</b></td><td>Next / previous without opening</td></tr>' +
263 '<tr><td style="padding:4px 16px 4px 0"><b>space</b></td><td>Page down or next item</td></tr>' +
264 '<tr><td style="padding:4px 16px 4px 0"><b>shift-space</b></td><td>Page up</td></tr>' +
265 '<tr><td style="padding:4px 16px 4px 0"><b>o / enter</b></td><td>Open / close item</td></tr>' +
266 '<tr><td style="padding:4px 16px 4px 0"><b>s</b></td><td>Toggle \u2b50 reaction</td></tr>' +
267 '<tr><td style="padding:4px 16px 4px 0"><b>v</b></td><td>Open original in new tab</td></tr>' +
268 '<tr><td style="padding:4px 16px 4px 0"><b>\u2318K / Ctrl-K</b></td><td>Search</td></tr>' +
269 '<tr><td style="padding:4px 16px 4px 0"><b>?</b></td><td>Show this help</td></tr>' +
270 '</table>' +
271 '<p style="margin:16px 0 0;color:light-dark(#536471,#71767b);font-size:13px">Press any key to close</p>' +
272 '</div>'
273 );
274 $( document ).one( 'keydown click', function() {
275 $( '#twitter-shortcuts-help' ).remove();
276 } );
277 }
278 return;
279 }
280 } );
281 } )( jQuery );
282
283 // @ mention autocomplete in the compose box.
284 ( function( $ ) {
285 var nonce = ( typeof friendsTwitter !== 'undefined' ) ? friendsTwitter.mentionNonce : '';
286 var $dropdown = null;
287 var currentTextarea = null;
288 var mentionStart = -1;
289 var activeIndex = -1;
290 var lastFetchedQuery = null;
291 var allResults = [];
292 var debounceTimer = null;
293 var noResultsTimer = null;
294
295 function getDropdown() {
296 if ( ! $dropdown ) {
297 $dropdown = $( '<ul class="twitter-mention-dropdown"></ul>' ).hide().appendTo( 'body' );
298 }
299 return $dropdown;
300 }
301
302 function closeDropdown() {
303 clearTimeout( noResultsTimer );
304 if ( $dropdown ) {
305 $dropdown.hide();
306 }
307 mentionStart = -1;
308 activeIndex = -1;
309 lastFetchedQuery = null;
310 allResults = [];
311 currentTextarea = null;
312 clearTimeout( debounceTimer );
313 }
314
315 function getMentionContext( textarea ) {
316 var text = textarea.value.substring( 0, textarea.selectionStart );
317 var match = text.match( /@(\w*)$/ );
318 if ( ! match ) {
319 return null;
320 }
321 return { query: match[ 1 ], start: text.length - match[ 0 ].length };
322 }
323
324 function positionDropdown( textarea ) {
325 var $form = $( textarea ).closest( '.twitter-compose-form' );
326 var offset = $form.offset();
327 getDropdown().css( {
328 top: offset.top + $form.outerHeight(),
329 left: offset.left,
330 width: $form.outerWidth()
331 } );
332 }
333
334 function setActive( index ) {
335 var $items = getDropdown().find( 'li:not(.twitter-mention-no-results)' );
336 $items.removeClass( 'active' );
337 if ( ! $items.length ) {
338 activeIndex = -1;
339 return;
340 }
341 activeIndex = Math.max( 0, Math.min( index, $items.length - 1 ) );
342 $items.eq( activeIndex ).addClass( 'active' );
343 }
344
345 function acceptActive() {
346 if ( activeIndex < 0 ) {
347 return false;
348 }
349 var $item = $dropdown.find( 'li:not(.twitter-mention-no-results)' ).eq( activeIndex ).find( 'a' );
350 if ( ! $item.length ) {
351 return false;
352 }
353 $item.trigger( 'mousedown' );
354 return true;
355 }
356
357 function buildItem( item ) {
358 var $li = $( '<li></li>' );
359 var $a = $( '<a href="#"></a>' );
360 if ( item.avatar ) {
361 $a.append( $( '<img>' ).attr( { src: item.avatar, width: 40, height: 40 } ) );
362 }
363 $a.append( $( '<span class="twitter-mention-name"></span>' ).text( item.display_name ) );
364 $a.append( $( '<span class="twitter-mention-handle"></span>' ).text( '@' + item.handle ) );
365 $a.on( 'mousedown', function( e ) {
366 e.preventDefault();
367 if ( ! currentTextarea ) {
368 return;
369 }
370 var value = currentTextarea.value;
371 var cursor = currentTextarea.selectionStart;
372 var mention = '@' + item.handle + ' ';
373 currentTextarea.value = value.substring( 0, mentionStart ) + mention + value.substring( cursor );
374 var pos = mentionStart + mention.length;
375 currentTextarea.setSelectionRange( pos, pos );
376 var ta = currentTextarea;
377 closeDropdown();
378 ta.focus();
379 } );
380 $li.append( $a );
381 return $li;
382 }
383
384 function renderResults( results, textarea ) {
385 clearTimeout( noResultsTimer );
386 var $dd = getDropdown();
387 $dd.empty();
388 if ( results.length ) {
389 results.forEach( function( item ) {
390 $dd.append( buildItem( item ) );
391 } );
392 positionDropdown( textarea );
393 $dd.show();
394 activeIndex = -1;
395 } else {
396 $dd.append( '<li class="twitter-mention-no-results"><span>No results</span></li>' );
397 positionDropdown( textarea );
398 $dd.show();
399 activeIndex = -1;
400 noResultsTimer = setTimeout( closeDropdown, 500 );
401 }
402 }
403
404 function filterAndRender( query, textarea ) {
405 var q = query.toLowerCase();
406 var filtered = allResults.filter( function( item ) {
407 return item.display_name.toLowerCase().indexOf( q ) !== -1 ||
408 item.handle.toLowerCase().indexOf( q ) !== -1;
409 } );
410 renderResults( filtered, textarea );
411 }
412
413 $( document ).on( 'input', '.twitter-compose-form textarea', function() {
414 var textarea = this;
415 var ctx = getMentionContext( textarea );
416
417 if ( ! ctx ) {
418 closeDropdown();
419 return;
420 }
421
422 currentTextarea = textarea;
423 mentionStart = ctx.start;
424
425 if ( allResults.length ) {
426 filterAndRender( ctx.query, textarea );
427 }
428
429 clearTimeout( debounceTimer );
430
431 if ( lastFetchedQuery !== null && ctx.query.indexOf( lastFetchedQuery ) === 0 ) {
432 return;
433 }
434
435 debounceTimer = setTimeout( function() {
436 var query = ctx.query;
437 wp.ajax.send( 'friends-mention-autocomplete', {
438 data: { _ajax_nonce: nonce, q: query },
439 success: function( results ) {
440 allResults = results || [];
441 lastFetchedQuery = query;
442
443 var currentCtx = getMentionContext( textarea );
444 if ( currentCtx ) {
445 filterAndRender( currentCtx.query, textarea );
446 } else {
447 closeDropdown();
448 }
449 },
450 error: function() {
451 closeDropdown();
452 }
453 } );
454 }, 200 );
455 } );
456
457 $( document ).on( 'keydown', '.twitter-compose-form textarea', function( e ) {
458 if ( ! $dropdown || ! $dropdown.is( ':visible' ) ) {
459 return;
460 }
461
462 var count = $dropdown.find( 'li:not(.twitter-mention-no-results)' ).length;
463
464 if ( 40 === e.keyCode ) {
465 e.preventDefault();
466 setActive( activeIndex < 0 ? 0 : ( activeIndex + 1 ) % count );
467 } else if ( 38 === e.keyCode ) {
468 e.preventDefault();
469 setActive( activeIndex < 0 ? count - 1 : ( activeIndex - 1 + count ) % count );
470 } else if ( 9 === e.keyCode ) {
471 if ( count ) {
472 e.preventDefault();
473 if ( activeIndex < 0 ) {
474 setActive( 0 );
475 }
476 acceptActive();
477 }
478 } else if ( 13 === e.keyCode && activeIndex >= 0 ) {
479 e.preventDefault();
480 acceptActive();
481 } else if ( 27 === e.keyCode ) {
482 closeDropdown();
483 }
484 } );
485
486 $( document ).on( 'blur', '.twitter-compose-form textarea', function() {
487 setTimeout( function() {
488 if ( $dropdown && $dropdown.is( ':visible' ) && ! $dropdown.find( ':focus' ).length ) {
489 closeDropdown();
490 }
491 }, 150 );
492 } );
493
494 $( document ).on( 'click', function( e ) {
495 if ( $dropdown && $dropdown.is( ':visible' ) ) {
496 if ( ! $( e.target ).closest( '.twitter-compose-form, .twitter-mention-dropdown' ).length ) {
497 closeDropdown();
498 }
499 }
500 } );
501 } )( jQuery );
502