Changeset 1105
- Timestamp:
- 01/08/2015 06:29:55 PM (10 years ago)
- Location:
- sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012
- Files:
-
- 3 added
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012/functions.php
r1085 r1105 36 36 add_action( 'init', array( __CLASS__, 'process_forms' ) ); 37 37 add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) ); 38 add_action( 'wp_ajax_get_latest_wordcamp_tweets', array( __CLASS__, 'get_latest_tweets' ) ); 39 add_action( 'wp_ajax_nopriv_get_latest_wordcamp_tweets', array( __CLASS__, 'get_latest_tweets' ) ); 38 40 39 41 add_filter( 'excerpt_more', array( __CLASS__, 'excerpt_more' ), 11 ); … … 181 183 static function enqueue_scripts() { 182 184 wp_enqueue_style( 'central', get_stylesheet_uri(), array(), 3 ); 183 wp_enqueue_script( 'wordcamp-central', get_stylesheet_directory_uri() . '/js/central.js', array(), '20140909', true ); 185 wp_enqueue_script( 'wordcamp-central', get_stylesheet_directory_uri() . '/js/central.js', array( 'jquery', 'underscore' ), 1, true ); 186 187 wp_localize_script( 'wordcamp-central', 'wordcampCentralOptions', array( 'ajaxURL' => admin_url( 'admin-ajax.php' ) ) ); 184 188 185 189 /* We add some JavaScript to pages with the comment form … … 425 429 public static function can_subscribe() { 426 430 return class_exists( 'Jetpack_Subscriptions' ) && is_callable( array( 'Jetpack_Subscriptions', 'subscribe' ) ); 431 } 432 433 /** 434 * Fetch the latest tweets from the @WordCamp account 435 * 436 * This is an AJAX callback returning JSON-formatted data. 437 * 438 * We're manually expiring/refreshing the transient to ensure that we only ever update it when we have a 439 * valid response from the API. If there is a problem retrieving new data from the API, then we want to 440 * continue displaying the valid cached data until we can successfully retrieve new data. The data is still 441 * stored in a transient instead of an option, though, so that it can be cached in memory. 442 * 443 * Under certain unlikely conditions, this could cause an API rate limit violation. If the data is expired 444 * and we can connect to the API at the network level, but then the request fails at the application level 445 * (invalid credentials, etc), then we'll be hitting the API every time this function is called. If that 446 * does ever happen, it could be fixed by setting the timestamp of the last attempt in a transient and only 447 * issuing another attempt if ~2 minutes have passed. 448 */ 449 public static function get_latest_tweets() { 450 $transient_key = 'wcc_latest_tweets'; 451 $tweets = get_transient( $transient_key ); 452 $expired = $tweets['last_update'] < strtotime( 'now - 15 minutes' ); 453 454 if ( ! $tweets || $expired ) { 455 $response = wp_remote_get( 456 'https://api.twitter.com/1.1/statuses/user_timeline.json?count=6&trim_user=true&exclude_replies=true&include_rts=false&screen_name=wordcamp', 457 array( 458 'headers' => array( 'Authorization' => 'Bearer ' . TWITTER_BEARER_TOKEN_WORDCAMP_CENTRAL ), 459 ) 460 ); 461 462 if ( ! is_wp_error( $response ) ) { 463 $tweets['tweets'] = json_decode( wp_remote_retrieve_body( $response ) ); 464 465 /* 466 * Remove all but the first 3 tweets 467 * 468 * The Twitter API includes retweets in the `count` parameter, even if include_rts=false is passed, 469 * so we have to request more tweets than we actually want and then cut it down here. 470 */ 471 if ( $tweets['tweets'] ) { 472 $tweets['tweets'] = array_slice( $tweets['tweets'], 0, 3 ); 473 $tweets['tweets'] = self::sanitize_format_tweets( $tweets['tweets'] ); 474 $tweets['last_update'] = time(); 475 476 set_transient( $transient_key, $tweets ); 477 } 478 } 479 } 480 481 wp_send_json_success( $tweets ); 482 } 483 484 /** 485 * Sanitize and format the tweet objects 486 * 487 * Whitelist the fields to cut down on how much data we're storing/transmitting, but also to force 488 * future devs to manually enable/sanitize any new fields that are used, which avoids the risk of 489 * accidentally using an unsafe value. 490 * 491 * @param array $tweets 492 * 493 * @return array 494 */ 495 protected static function sanitize_format_tweets( $tweets ) { 496 $whitelisted_fields = array( 'id_str' => '', 'text' => '', 'created_at' => '' ); 497 498 foreach ( $tweets as & $tweet ) { 499 $tweet = (object) shortcode_atts( $whitelisted_fields, $tweet ); 500 $tweet->id_str = sanitize_text_field( $tweet->id_str ); 501 $tweet->text = wp_kses( $tweet->text, wp_kses_allowed_html( 'data' ), array( 'http', 'https', 'mailto' ) ); 502 $tweet->text = make_clickable( $tweet->text ); 503 $tweet->text = self::link_hashtags_and_usernames( $tweet->text ); 504 $tweet->time_ago = human_time_diff( strtotime( $tweet->created_at ) ); 505 } 506 507 return $tweets; 508 } 509 510 /** 511 * Convert usernames and hashtags to links 512 * 513 * Based on Tagregator's TGGRSourceTwitter::link_hashtags_and_usernames(). 514 * 515 * @param string $text 516 * 517 * @return string 518 */ 519 protected static function link_hashtags_and_usernames( $content ) { 520 $content = preg_replace( '/@(\w+)/', '<a href="https://twitter.com/\\1" class="wc-tweets-username">@\\1</a>', $content ); 521 $content = preg_replace( '/(?<!&)#(\w+)/', '<a href="https://twitter.com/search?q=\\1" class="wc-tweets-tag" >#\\1</a>', $content ); 522 523 return $content; 427 524 } 428 525 -
sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012/js/central.js
r1085 r1105 1 /** 2 * Toggle the navigation menu for small screens. 3 */ 4 ( function() { 5 var container, button, menu; 1 var WordCampCentral = ( function( $ ) { 6 2 7 container = document.getElementById( 'access' ); 8 if ( ! container ) { 9 return; 3 // templateOptions is copied from Core in order to avoid an extra HTTP request just to get wp.template 4 var ajaxURL, 5 templateOptions = { 6 evaluate: /<#([\s\S]+?)#>/g, 7 interpolate: /\{\{\{([\s\S]+?)\}\}\}/g, 8 escape: /\{\{([^\}]+?)\}\}(?!\})/g 9 }; 10 11 /** 12 * Initialization that runs as soon as this file has loaded 13 */ 14 function immediateInit( options ) { 15 ajaxURL = options.ajaxURL; 16 17 toggleNavigation(); 18 populateLatestTweets(); 10 19 } 11 20 12 button = container.getElementsByTagName( 'button' )[0]; 13 if ( 'undefined' === typeof button ) { 14 return; 21 /** 22 * Initialization that runs when the document has fully loaded 23 */ 24 function documentReadyInit() { 15 25 } 16 26 17 menu = container.getElementsByTagName( 'ul' )[0]; 27 /** 28 * Toggle the navigation menu for small screens. 29 */ 30 function toggleNavigation() { 31 var container, button, menu; 18 32 19 // Hide menu toggle button if menu is empty and return early. 20 if ( 'undefined' === typeof menu ) { 21 button.style.display = 'none'; 22 return; 33 container = document.getElementById( 'access' ); 34 if ( ! container ) { 35 return; 36 } 37 38 button = container.getElementsByTagName( 'button' )[0]; 39 if ( 'undefined' === typeof button ) { 40 return; 41 } 42 43 menu = container.getElementsByTagName( 'ul' )[0]; 44 45 // Hide menu toggle button if menu is empty and return early. 46 if ( 'undefined' === typeof menu ) { 47 button.style.display = 'none'; 48 return; 49 } 50 51 if ( -1 === menu.className.indexOf( 'nav-menu' ) ) { 52 menu.className += ' nav-menu'; 53 } 54 55 button.onclick = function() { 56 if ( -1 !== container.className.indexOf( 'toggled' ) ) { 57 container.className = container.className.replace( ' toggled', '' ); 58 } else { 59 container.className += ' toggled'; 60 } 61 }; 23 62 } 24 63 25 if ( -1 === menu.className.indexOf( 'nav-menu' ) ) { 26 menu.className += ' nav-menu'; 64 /** 65 * Fetch the latest tweets and inject them into the DOM 66 */ 67 function populateLatestTweets() { 68 $.getJSON( 69 ajaxURL, 70 { action: 'get_latest_wordcamp_tweets' }, 71 function( response ) { 72 var index, tweets, 73 spinner = $( '#wc-tweets-spinner' ), 74 error = $( '#wc-tweets-error' ), 75 tweetsContainer = $( '#wc-tweets-container' ), 76 tweetTemplate = _.template( $( '#tmpl-wc-tweet' ).html(), null, templateOptions ); 77 78 // Check for success 79 if ( response.hasOwnProperty( 'data' ) && response.data.hasOwnProperty( 'tweets' ) ) { 80 tweets = response.data.tweets; 81 } else { 82 spinner.addClass( 'hidden' ); 83 error.removeClass( 'hidden' ); 84 error.removeAttr( 'hidden' ); 85 return; 86 } 87 88 // Populate and reveal the container 89 for ( index in tweets ) { 90 if ( tweets.hasOwnProperty( index ) ) { 91 tweetsContainer.append( tweetTemplate( { 'tweet': tweets[ index ] } ) ); 92 } 93 } 94 95 spinner.addClass( 'hidden' ); 96 tweetsContainer.removeClass( 'transparent' ); 97 } 98 ); 27 99 } 28 100 29 button.onclick = function() { 30 if ( -1 !== container.className.indexOf( 'toggled' ) ) { 31 container.className = container.className.replace( ' toggled', '' ); 32 } else { 33 container.className += ' toggled'; 34 } 101 return { 102 immediateInit: immediateInit, 103 documentReadyInit: documentReadyInit 35 104 }; 105 } )( jQuery ); 36 106 37 } )(); 107 WordCampCentral.immediateInit( wordcampCentralOptions ); 108 jQuery( document ).ready( WordCampCentral.documentReadyInit ); -
sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012/style.css
r852 r1105 335 335 *:first-child+html .group, 336 336 *:first-child+html li { min-height: 1px } 337 .clearfix:after { 338 content: ""; 339 display: table; 340 clear: both; 341 } 337 342 338 343 /* !01d. Buttons */ … … 513 518 } 514 519 520 .spinner { 521 background: url( 'images/spinner.gif' ) no-repeat; 522 background-size: 20px 20px; 523 display: none; 524 float: right; 525 opacity: 0.7; 526 filter: alpha(opacity=70); 527 width: 20px; 528 height: 20px; 529 margin: 2px 5px 0; 530 } 531 532 .spinner-visible { 533 display: block; 534 } 535 536 .hidden { 537 display: none; 538 } 539 540 .transparent { 541 opacity: 0; 542 } 543 515 544 /* !01f. Header & Footer */ 516 545 /* ------------------------------------- */ … … 1021 1050 } 1022 1051 #wc-content-blocks .wc-news h3 { background-position: 0 -71px } 1052 #wc-content-blocks .wc-tweets h3 { 1053 background-position: 0 -658px; 1054 padding-left: 40px; 1055 margin-bottom: 9px; 1056 } 1023 1057 #wc-content-blocks .wc-sessions h3 { 1024 1058 background-position: -7px -150px; … … 1119 1153 } 1120 1154 1155 1156 /* !Latest Tweets */ 1157 #wc-tweets-spinner { 1158 float: none; 1159 margin: 0 auto; 1160 } 1161 1162 #wc-tweets-container { 1163 transition: all 1.25s ease; 1164 } 1165 #wc-tweets-container li { 1166 position: relative; 1167 border-top: 1px solid #EEEEEE; 1168 padding: 1em 0; 1169 } 1170 1171 .wc-tweet-timestamp { 1172 position: absolute; 1173 bottom: 1em; /* matches #wc-tweets-container li margin-bottom */ 1174 right: 0; 1175 margin-bottom: 0; 1176 } 1177 1178 #wc-tweets-container li .wc-tweet-actions li { 1179 float: left; 1180 margin-right: 12px; 1181 padding-top: 8px; 1182 padding-bottom: 0; 1183 border-top: none; 1184 } 1185 .wc-tweet-actions li a, 1186 .wc-tweet-timestamp a { 1187 font-weight: normal; 1188 color: #b2b2b2; 1189 } 1190 .wc-tweet-actions li a:hover, 1191 .wc-tweet-timestamp a:hover { 1192 color: #666666; 1193 } 1194 .wc-tweet-action-icon { 1195 position: relative; 1196 top: 3px; 1197 display: inline-block; 1198 width: 16px; 1199 height: 16px; 1200 background-image: url( 'images/twitter-actions.png' ); 1201 background-repeat: no-repeat; 1202 } 1203 .wc-tweet-action-reply .wc-tweet-action-icon { 1204 background-position: 0 0; 1205 } 1206 .wc-tweet-action-reply:hover .wc-tweet-action-icon { 1207 background-position: -16px 0; 1208 } 1209 .wc-tweet-action-retweet .wc-tweet-action-icon { 1210 width: 19px; 1211 background-position: -80px 0; 1212 } 1213 .wc-tweet-action-retweet:hover .wc-tweet-action-icon { 1214 background-position: -99px 0; 1215 } 1216 .wc-tweet-action-favorite .wc-tweet-action-icon { 1217 top: 2px; 1218 background-position: -32px 0; 1219 } 1220 .wc-tweet-action-favorite:hover .wc-tweet-action-icon { 1221 background-position: -48px 0; 1222 } 1121 1223 1122 1224 /* !WordCamp Sessions */ … … 2651 2753 /* ============================================= */ 2652 2754 @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) { 2755 .spinner { 2756 background-image: url( 'images/spinner-2x.gif' ); 2757 } 2653 2758 #access .sub-menu, #header { 2654 2759 background: #e0f5fa url('images/header-bg-2x.png'); … … 2684 2789 #wc-content-blocks .wc-news h3 { 2685 2790 background-position: 0 -71px; 2791 } 2792 #wc-content-blocks .wc-tweets h3 { 2793 background-position: 0 -658px; 2686 2794 } 2687 2795 #wc-content-blocks .wc-sessions h3 { … … 2904 3012 margin-right: 0; 2905 3013 } 3014 3015 .wc-tweet-timestamp { 3016 position: relative; 3017 bottom: auto; 3018 right: auto; 3019 margin-top: 10px; 3020 } 3021 .wc-tweet-timestamp a::after { 3022 content: " ago" 3023 } 3024 #wc-tweets-container li .wc-tweet-actions li { 3025 padding-top: 0; 3026 } 2906 3027 } 2907 3028 -
sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012/template-home.php
r842 r1105 89 89 </div><!-- .wc-news --> 90 90 91 <div class="wc- sessions last">92 <h3> WordCamp <strong>Sessions</strong></h3>91 <div class="wc-tweets last"> 92 <h3><strong>Latest Tweets</strong></h3> 93 93 94 <?php $sessions = WordCamp_Central_Theme::get_sessions(); ?> 95 <?php if ( ! empty( $sessions ) ) : ?> 96 <ul> 97 <?php foreach( $sessions as $session ) : ?> 94 <div id="wc-tweets-spinner" class="spinner spinner-visible"></div> 95 <ul id="wc-tweets-container" class="transparent"></ul> 96 97 <p id="wc-tweets-error" class="hidden" hidden> 98 Tweets from <a href="https://twitter.com/wordcamp">@WordCamp</a> are currently unavailable. 99 </p> 100 101 <a href="https://twitter.com/wordcamp" class="more">Follow @WordCamp on Twitter →</a> 102 103 <script id="tmpl-wc-tweet" type="text/html"> 98 104 <li> 99 <?php echo $session['wordcamp_thumb']; ?> 100 <a href="<?php echo esc_url( $session['permalink'] ); ?>" class="wc-session-name"><?php echo esc_html( $session['name'] ); ?></a> 101 <span class="wc-session-speakers">by <?php echo esc_html( $session['speakers'] ); ?></span> 102 <a href="<?php echo esc_url( $session['wordcamp_permalink'] ); ?>" class="wc-session-wordcamp"><?php echo esc_html( $session['wordcamp_title'] ); ?></a> 105 <div class="wc-tweet-content">{{{tweet.text}}}</div> 106 107 <p class="wc-tweet-timestamp"> 108 <a href="https://twitter.com/wordcamp/status/{{tweet.id_str}}">{{tweet.time_ago}}</a> 109 </p> 110 111 <ul class="wc-tweet-actions clearfix"> 112 <li class="wc-tweet-action-reply"> 113 <a href="https://twitter.com/intent/tweet?in_reply_to={{tweet.id_str}}"> 114 <span class="wc-tweet-action-icon"></span> 115 Reply 116 </a> 117 </li> 118 119 <li class="wc-tweet-action-retweet"> 120 <a href="https://twitter.com/intent/retweet?tweet_id={{tweet.id_str}}"> 121 <span class="wc-tweet-action-icon"></span> 122 Retweet 123 </a> 124 </li> 125 126 <li class="wc-tweet-action-favorite"> 127 <a href="https://twitter.com/intent/favorite?tweet_id={{tweet.id_str}}"> 128 <span class="wc-tweet-action-icon"></span> 129 Favorite 130 </a> 131 </li> 132 </ul> 103 133 </li> 104 <?php endforeach; ?> 105 </ul> 106 <!--<a href="#" class="more">More Speakers →</a>--> 107 <?php else : // ! empty( $sessions ) ?> 108 <p>We're sorry, the speakers list is temporarily unavailable.</p> 109 <?php endif; ?> 110 </div> <!-- .wc-speakers --> 134 </script> 135 </div> <!-- .wc-tweets --> 111 136 112 137 </div> <!-- #wc-content-blocks -->
Note: See TracChangeset
for help on using the changeset viewer.