diff --git a/www/plugins/jasonwilliams/feed/Plugin.php b/www/plugins/jasonwilliams/feed/Plugin.php new file mode 100644 index 0000000..39252a4 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/Plugin.php @@ -0,0 +1,39 @@ + [ + 'label' => 'Feed', + 'description' => 'Manage API keys and settings related to the feed plugin', + 'icon' => 'icon-newspaper-o', + 'class' => 'JasonWilliams\Feed\Models\Settings', + 'order' => 500, + ] + ]; + } + + public function registerComponents() + { + return [ + '\JasonWilliams\Feed\Components\ShortFeed' => 'ShortFeed', + '\JasonWilliams\Feed\Components\LazyLoadShortFeed' => 'LazyLoadShortFeed', + '\JasonWilliams\Feed\Components\ChannelList' => 'ChannelList', + '\JasonWilliams\Feed\Components\TagList' => 'TagList' + ]; + } + + public function registerSchedule($schedule) + { + $schedule->call(function() { + SocialApis::updateTwitter(); + SocialApis::updateInstagram(); + SocialApis::updateFoursquare(); + })->everyThirtyMinutes(); + } +} diff --git a/www/plugins/jasonwilliams/feed/assets/javascript/lazyloadshortfeed.js b/www/plugins/jasonwilliams/feed/assets/javascript/lazyloadshortfeed.js new file mode 100644 index 0000000..c091444 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/assets/javascript/lazyloadshortfeed.js @@ -0,0 +1,10 @@ +$(() => { + var obj = {}; + if (typeof renderPartial !== 'string') obj = { '@feed': '#feedarea' } + else obj[renderPartial] = '#feedarea' + + $.request('LazyLoadShortFeed::onUpdateRequested', { + update: obj, + complete: () => { $(window).trigger('feedLoaded') } + }) +}) diff --git a/www/plugins/jasonwilliams/feed/classes/SocialApis.php b/www/plugins/jasonwilliams/feed/classes/SocialApis.php new file mode 100644 index 0000000..a71820b --- /dev/null +++ b/www/plugins/jasonwilliams/feed/classes/SocialApis.php @@ -0,0 +1,287 @@ + 0) + { + foreach ($tweets as $tweet) + { + // Skip tweets created by IFTTT if they have an image attached (because they came from Instagram) + if (!isset($tweet['retweet_status']) && strpos($tweet['source'], "IFTTT") && isset($tweet['entities']['media'])) continue; + + // Skip any tweets created by Foursquare + if (!isset($tweet['retweet_status']) && strpos($tweet['source'], "Foursquare")) continue; + + $extradata = array(); + + // Get the timestamp before we go down a layer to retweets + $timestamp = strtotime($tweet['created_at']); + + // Set extra data if this is a retweet + if (isset($tweet['retweeted_status'])) + { + $tweet = $tweet['retweeted_status']; + $extradata['opuser'] = $tweet['user']['screen_name']; + $extradata['opname'] = $tweet['user']['name']; + $extradata['opimg'] = $tweet['user']['profile_image_url_https']; + } + + // Set extra data if there's an attached image + if (isset($tweet['entities']['media'])) $extradata['img'] = $tweet['entities']['media'][0]['media_url_https']; + + // Convert any extra data into a JSON object + $extradata = (sizeof($extradata) > 0) ? json_encode($extradata) : ""; + + // Create a new FeedItem + $feeditem = new FeedItem; + $feeditem->timestamp = $timestamp; + $feeditem->channel_id = 2; + $feeditem->title = $tweet['full_text']; + $feeditem->link = "https://twitter.com/i/web/status/".$tweet['id_str']; + $feeditem->extra = $extradata; + $feeditem->save(); + + // Are there links to include? + if (sizeof($tweet['entities']['urls']) > 0) + { + foreach ($tweet['entities']['urls'] as $url) + { + $feeditem->links()->create([ + 'addr' => $url['url'], + 'display' =>$url['display_url'] + ]); + } + } + + // Are there tags to include? + if (sizeof($tweet['entities']['hashtags'])) + { + foreach ($tweet['entities']['hashtags'] as $tag) + { + $feeditem->tags()->create([ + 'tag' => $tag['text'] + ]); + } + } + } + + // Set the latest item ID + Settings::set('twitter_latest_tweet', $tweets[sizeof($tweets) - 1]['id_str']); + } + } + + // Update the database with the latest instagram posts + public static function updateInstagram() + { + $posts = self::fetchInstagram(); + + if (sizeof($posts) > 0) + { + foreach ($posts as $post) + { + // Skip videos and albums + if ($post['media_type'] != "IMAGE") continue; + + // Create a new FeedItem + $feeditem = new FeedItem; + $feeditem->timestamp = strtotime($post['timestamp']); + $feeditem->channel_id = 3; + $feeditem->title = (isset($post['caption'])) ? $post['caption'] : ""; + $feeditem->link = $post['permalink']; + $feeditem->extra = json_encode(['img' => $post['media_url']]); + $feeditem->save(); + + // Are there tags to include? + if (isset($post['caption']) && strpos($post['caption'], '#')) + { + preg_match_all('%\B#\w*[a-zA-Z]+\w*%', $post['caption'], $hashtags); + foreach ($hashtags[0] as $tag) + { + $feeditem->tags()->create([ + 'tag' => substr($tag, 1) + ]); + } + } + } + + // Set the latest item ID + Settings::set('instagram_latest_post', strtotime(end($posts)['timestamp'])); + } + } + + // Update the database with the latest foursquare posts + public static function updateFoursquare() + { + $posts = self::fetchFoursquare(); + + if (sizeof($posts) > 0) + { + foreach ($posts as $post) + { + $extradata = [ + 'lat' => $post['venue']['location']['lat'], + 'lng' => $post['venue']['location']['lng'], + 'address' => [] + ]; + + // Build the address + if (isset($post['venue']['location']['address'])) $extradata['address'][] = $post['venue']['location']['address']; + if (isset($post['venue']['location']['city'])) $extradata['address'][] = $post['venue']['location']['city']; + if (isset($post['venue']['location']['country'])) $extradata['address'][] = $post['venue']['location']['country']; + $extradata['address'] = implode(', ', $extradata['address']); + + // Create a new FeedItem + $feeditem = new FeedItem; + $feeditem->timestamp = $post['createdAt']; + $feeditem->channel_id = 6; + $feeditem->title = $post['venue']['name']; + $feeditem->body = (isset($post['shout'])) ? $post['shout'] : ""; + $feeditem->extra = json_encode($extradata); + $feeditem->save(); + + // Are there tags to include? + if (isset($post['shout']) && strpos($post['shout'], '#')) + { + preg_match_all('%\B#\w*[a-zA-Z]+\w*%', $post['shout'], $hashtags); + foreach ($hashtags[0] as $tag) + { + $feeditem->tags()->create([ + 'tag' => substr($tag, 1) + ]); + } + } + } + + // Set the latest item ID + Settings::set('foursquare_latest_post', end($posts)['createdAt']); + } + } + + // Fetch and return the latest tweets + private static function fetchTwitter() + { + // Set the API endpoint + $endpoint = "https://api.twitter.com/1.1/statuses/user_timeline.json"; + + // Set request parameteres + $req_params = array( + 'screen_name' => Settings::get('twitter_username'), + 'count' => 200, + 'since_id' => Settings::get('twitter_latest_tweet'), + 'tweet_mode' => 'extended', + 'exclude_replies' => true, + 'include_rts' => true + ); + + // Set oauth parameters + self::$authobject = array( + 'oauth_token' => Settings::get('twitter_access_token'), + 'oauth_consumer_key' => Settings::get('twitter_consumer_key'), + 'oauth_nonce' => time(), + 'oauth_signature_method' => 'HMAC-SHA1', + 'oauth_timestamp' => time(), + 'oauth_version' => '1.0' + ); + + // Add parameters to oauth array + self::$authobject = array_merge(self::$authobject, $req_params); + + // Generate the oauth signautre + $base_string = self::buildBaseString($endpoint, "GET", self::$authobject); + $composite_key = rawurlencode(Settings::get('twitter_consumer_secret'))."&".rawurlencode(Settings::get('twitter_access_token_secret')); + self::$authobject['oauth_signature'] = base64_encode(hash_hmac('sha1', $base_string, $composite_key, true)); + + // Create the URL by adding the parameters to the endpoint + $endpoint = $endpoint."?".http_build_query($req_params); + + $tweets = array_reverse(json_decode(Http::get($endpoint, function($http) + { + $http->header(self::buildAuthorizationHeader(self::$authobject)); + }), true)); + + return $tweets; + } + + // Fetch and return the latest instagram posts + private static function fetchInstagram() + { + $endpoint = "https://graph.instagram.com/me/media"; + + // Set the fields we want back + $fields = [ + 'id', + 'ig-id', + 'caption', + 'media_type', + 'media_url', + 'permalink', + 'timestamp' + ]; + + // Create the URL by adding the parameters to the endpoint + $endpoint = $endpoint."?".http_build_query([ + 'access_token' => Settings::get('instagram_access_token'), + 'fields' => implode(',', $fields), + 'limit' => 100 + ]); + + $posts = json_decode(Http::get($endpoint), true); + $posts = array_reverse($posts['data']); + + foreach($posts as $postkey => $post) + { + if (strtotime($post['timestamp']) <= Settings::get('instagram_latest_post')) unset($posts[$postkey]); + } + + return $posts; + } + + // Fetch and return the latest foursquare checkins + private static function fetchFoursquare() + { + $endpoint = "https://api.foursquare.com/v2/users/self/checkins"; + + // Create the URL by adding the parameters to the endpoint + $endpoint = $endpoint."?".http_build_query([ + 'oauth_token' => Settings::get('foursquare_access_token'), + 'v' => date('Ymd'), + 'limit' => 250, + 'sort' => 'oldestfirst', + 'afterTimestamp' => Settings::get('foursquare_latest_post') + 1 + ]); + + $posts = json_decode(Http::get($endpoint), true); + return $posts['response']['checkins']['items']; + } + + // Function to build a base string for the twitter API + private static function buildBaseString($baseURI, $method, $params) { + $r = array(); + ksort($params); + foreach ($params as $key => $value) { + $r[] = $key.'='.rawurlencode($value); + } + return $method.'&'.rawurlencode($baseURI).'&'.rawurlencode(implode('&', $r)); + } + + // Function to build the OAuth authorization header for twitter + private static function buildAuthorizationHeader($oauth) { + $r = 'Authorization: OAuth '; + $values = array(); + foreach($oauth as $key=>$value) + $values[] = $key.'="'.rawurlencode($value).'"'; + $r .= implode(', ', $values); + return $r; + } + + private static $authobject; +} diff --git a/www/plugins/jasonwilliams/feed/components/ChannelList.php b/www/plugins/jasonwilliams/feed/components/ChannelList.php new file mode 100644 index 0000000..4d188eb --- /dev/null +++ b/www/plugins/jasonwilliams/feed/components/ChannelList.php @@ -0,0 +1,41 @@ + 'Channel List', + 'description' => 'Displays a list of feed channels.' + ]; + } + + public function defineProperties() + { + return [ + 'includeEmpty' => [ + 'title' => 'Include Empty?', + 'description' => 'Include channels with no posts?', + 'type' => 'checkbox', + 'default' => true + ] + ]; + } + + public function onRun() + { + $this->page['channels'] = Channels::orderBy('id')->withCount('feeditems')->get(); + + // If applicable, remove empty channels + if (!$this->property('includeEmpty')) + { + foreach($this->page['channels'] as $key => $value) + { + if (!$value->feeditems_count) unset($this->page['channels'][$key]); + } + } + } +} diff --git a/www/plugins/jasonwilliams/feed/components/LazyLoadShortFeed.php b/www/plugins/jasonwilliams/feed/components/LazyLoadShortFeed.php new file mode 100644 index 0000000..5b3cd8c --- /dev/null +++ b/www/plugins/jasonwilliams/feed/components/LazyLoadShortFeed.php @@ -0,0 +1,47 @@ + 'Short Feed (Lazy Load)', + 'description' => 'Displays a mini-feed of the most recent feed items, retrieved after other page content has been loaded' + ]; + } + + public function defineProperties() + { + return [ + 'maxItems' => [ + 'title' => 'Max items', + 'description' => 'How many feed items should be displayed?', + 'default' => 10, + 'type' => 'string', + 'validationPattern' => '^[0-9]+$', + 'validationMessage' => 'The Max items property must be numeric' + ], + 'renderPartial' => [ + 'title' => 'Render partial', + 'description' => 'The path to the partial that will be used as a template to render feed items. @feed is provided by the plugin itself.', + 'default' => '@feed', + 'type' => 'string' + ] + ]; + } + + public function onRun() + { + $this->page['renderPartial'] = $this->property('renderPartial'); + $this->addJs('/plugins/jasonwilliams/feed/assets/javascript/lazyloadshortfeed.js'); + } + + public function onUpdateRequested() + { + date_default_timezone_set('America/Edmonton'); + $this->page['posts'] = FeedItem::orderBy('timestamp', 'desc')->take($this->property('maxItems'))->get(); + } +} diff --git a/www/plugins/jasonwilliams/feed/components/ShortFeed.php b/www/plugins/jasonwilliams/feed/components/ShortFeed.php new file mode 100644 index 0000000..748acf0 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/components/ShortFeed.php @@ -0,0 +1,79 @@ + 'Short Feed', + 'description' => 'Displays a mini-feed of the most recent feed items, loaded with other page content' + ]; + } + + public function defineProperties() + { + return [ + 'channelFilter' => [ + 'title' => 'Channels', + 'description' => 'A comma-separated list of channels to include in feed.', + 'type' => 'string', + 'default' => '' + ], + 'tagFilter' => [ + 'title' => 'Tag', + 'description' => 'The tag to display in the feed.', + 'type' => 'string', + 'default' => '' + ], + 'maxItems' => [ + 'title' => 'Max items', + 'description' => 'How many feed items should be displayed?', + 'default' => 10, + 'type' => 'string', + 'validationPattern' => '^[0-9]+$', + 'validationMessage' => 'The Max items property must be numeric' + ] + ]; + } + + public function onRun() + { + date_default_timezone_set('America/Edmonton'); + + // Set up the results query + $results = FeedItem::orderBy('timestamp', 'desc')->take($this->property('maxItems')); + + // Do we need to filter based on a tag? + if ($this->property('tagFilter') != null) + { + $results->join('jasonwilliams_feed_tags', 'jasonwilliams_feed_.id', '=', 'jasonwilliams_feed_tags.feed_item_id'); + $results->where('tag', $this->property('tagFilter')); + } + + // Do we need to filter based on the channel? + if ($this->property('channelFilter') != null && $this->property('channelFilter') != 'all') + { + $this->channelfilter = explode(',', $this->property('channelFilter')); + + $results->whereHas('channel', function($q) { + $firstchannel = true; + + foreach ($this->channelfilter as $channel) + { + if ($firstchannel) $q->where('slug', $channel); + else $q->orWhere('slug', $channel); + + $firstchannel = false; + } + }); + } + + $this->page['posts'] = $results->get(); + } +} diff --git a/www/plugins/jasonwilliams/feed/components/TagList.php b/www/plugins/jasonwilliams/feed/components/TagList.php new file mode 100644 index 0000000..5dfbe47 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/components/TagList.php @@ -0,0 +1,26 @@ + 'Tag List', + 'description' => 'Displays a list of feed tags.' + ]; + } + + public function defineProperties() + { + return []; + } + + public function onRun() + { + $this->page['tags'] = Tags::groupBy('tag')->select(Db::raw('tag, count(*) as count'))->orderBy('count', 'desc')->get(); + } +} diff --git a/www/plugins/jasonwilliams/feed/components/channellist/default.htm b/www/plugins/jasonwilliams/feed/components/channellist/default.htm new file mode 100644 index 0000000..e00ee5f --- /dev/null +++ b/www/plugins/jasonwilliams/feed/components/channellist/default.htm @@ -0,0 +1,5 @@ +
= e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/www/plugins/jasonwilliams/feed/controllers/channels/index.htm b/www/plugins/jasonwilliams/feed/controllers/channels/index.htm new file mode 100644 index 0000000..ea43a36 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/controllers/channels/index.htm @@ -0,0 +1 @@ += $this->listRender() ?> diff --git a/www/plugins/jasonwilliams/feed/controllers/channels/preview.htm b/www/plugins/jasonwilliams/feed/controllers/channels/preview.htm new file mode 100644 index 0000000..a5fd5a0 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/controllers/channels/preview.htm @@ -0,0 +1,22 @@ + ++ + = e(trans('backend::lang.form.return_to_list')) ?> + +
\ No newline at end of file diff --git a/www/plugins/jasonwilliams/feed/controllers/channels/reorder.htm b/www/plugins/jasonwilliams/feed/controllers/channels/reorder.htm new file mode 100644 index 0000000..cf71506 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/controllers/channels/reorder.htm @@ -0,0 +1,8 @@ + += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/www/plugins/jasonwilliams/feed/controllers/feed/_list_toolbar.htm b/www/plugins/jasonwilliams/feed/controllers/feed/_list_toolbar.htm new file mode 100644 index 0000000..1ce9228 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/controllers/feed/_list_toolbar.htm @@ -0,0 +1,19 @@ += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/www/plugins/jasonwilliams/feed/controllers/feed/index.htm b/www/plugins/jasonwilliams/feed/controllers/feed/index.htm new file mode 100644 index 0000000..ea43a36 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/controllers/feed/index.htm @@ -0,0 +1 @@ += $this->listRender() ?> diff --git a/www/plugins/jasonwilliams/feed/controllers/feed/preview.htm b/www/plugins/jasonwilliams/feed/controllers/feed/preview.htm new file mode 100644 index 0000000..34d964a --- /dev/null +++ b/www/plugins/jasonwilliams/feed/controllers/feed/preview.htm @@ -0,0 +1,22 @@ + ++ + = e(trans('backend::lang.form.return_to_list')) ?> + +
\ No newline at end of file diff --git a/www/plugins/jasonwilliams/feed/controllers/feed/reorder.htm b/www/plugins/jasonwilliams/feed/controllers/feed/reorder.htm new file mode 100644 index 0000000..459cc3c --- /dev/null +++ b/www/plugins/jasonwilliams/feed/controllers/feed/reorder.htm @@ -0,0 +1,8 @@ + += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/www/plugins/jasonwilliams/feed/lang/en-ca/lang.php b/www/plugins/jasonwilliams/feed/lang/en-ca/lang.php new file mode 100644 index 0000000..0d04f01 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/lang/en-ca/lang.php @@ -0,0 +1,6 @@ + [ + 'name' => 'Social Feed', + 'description' => 'Captures social media posts from a variety of services and consolidates them into a single feed.' + ] +]; diff --git a/www/plugins/jasonwilliams/feed/models/Channels.php b/www/plugins/jasonwilliams/feed/models/Channels.php new file mode 100644 index 0000000..5d677c9 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/Channels.php @@ -0,0 +1,33 @@ + ['jasonwilliams\feed\Models\FeedItem', 'key' => 'channel_id'] + ]; +} diff --git a/www/plugins/jasonwilliams/feed/models/FeedItem.php b/www/plugins/jasonwilliams/feed/models/FeedItem.php new file mode 100644 index 0000000..b1cb2e4 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/FeedItem.php @@ -0,0 +1,65 @@ + 'jasonwilliams\feed\Models\Links', + 'tags' => 'jasonwilliams\feed\Models\Tags' + ]; + + public $belongsTo = [ + 'channel' => ['jasonwilliams\feed\Models\Channels', 'key' => 'channel_id'] + ]; + + public function afterFetch() + { + // Add clickable hashtags to the title field + $this->title = preg_replace('/#(\w*[a-zA-Z]+\w*)/', '#$1', $this->title); + + // Add a friendlytime field + $this->friendlytime = date('M j, Y', $this->timestamp); + + // Expand extras to an object + if ($this->extra) $this->extra = json_decode($this->extra); + + // Add twitter user links + if ($this->channel_id == 2) $this->title = preg_replace('/@([a-zA-Z0-9_]+)/', '@$1', $this->title); + + // Add instagram user links + if ($this->channel_id == 3) $this->title = preg_replace('/@([a-zA-Z0-9_]+)/', '@$1', $this->title); + + // Add foursquare user Links + if ($this->channel_id == 6 && !empty($this->body)) $this->body = preg_replace('/@([a-zA-Z0-9_]+)/', '@$1', $this->body); + + // Find and replace links + foreach($this->links as $link) + { + $this->title = str_replace($link->addr, "addr."\">".$link->display."", $this->title); + } + } +} diff --git a/www/plugins/jasonwilliams/feed/models/Links.php b/www/plugins/jasonwilliams/feed/models/Links.php new file mode 100644 index 0000000..d12c73a --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/Links.php @@ -0,0 +1,35 @@ + 'jasonwilliams\feed\Models\FeedItem' + ]; +} diff --git a/www/plugins/jasonwilliams/feed/models/Settings.php b/www/plugins/jasonwilliams/feed/models/Settings.php new file mode 100644 index 0000000..1975cf7 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/Settings.php @@ -0,0 +1,14 @@ + ['jasonwilliams\feed\Models\FeedItem', 'key' => 'feed_item_id'] + ]; + + public function afterFetch() + { + //dd($this->feeditem); + } +} diff --git a/www/plugins/jasonwilliams/feed/models/channels/columns.yaml b/www/plugins/jasonwilliams/feed/models/channels/columns.yaml new file mode 100644 index 0000000..cc0e2af --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/channels/columns.yaml @@ -0,0 +1,10 @@ +columns: + id: + label: 'Channel ID' + type: number + sortable: true + title: + label: Title + type: text + searchable: true + sortable: false diff --git a/www/plugins/jasonwilliams/feed/models/channels/fields.yaml b/www/plugins/jasonwilliams/feed/models/channels/fields.yaml new file mode 100644 index 0000000..0f34f6d --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/channels/fields.yaml @@ -0,0 +1,21 @@ +fields: + title: + label: 'Channel Title' + span: left + type: text + slug: + label: Slug + span: right + preset: + field: title + type: slug + type: text + icon: + label: Icon + span: left + type: text + comment: 'Icon code from FontAwesome' + colour: + label: 'Icon Colour' + span: auto + type: colorpicker diff --git a/www/plugins/jasonwilliams/feed/models/feeditem/columns.yaml b/www/plugins/jasonwilliams/feed/models/feeditem/columns.yaml new file mode 100644 index 0000000..0ae8fbe --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/feeditem/columns.yaml @@ -0,0 +1,9 @@ +columns: + title: + label: 'Post Title' + type: text + searchable: true + timestamp: + label: 'UNIX Timestamp' + type: number + sortable: true diff --git a/www/plugins/jasonwilliams/feed/models/feeditem/fields.yaml b/www/plugins/jasonwilliams/feed/models/feeditem/fields.yaml new file mode 100644 index 0000000..0b628d3 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/feeditem/fields.yaml @@ -0,0 +1,20 @@ +fields: + title: + label: 'Post Title' + span: full + type: text + body: + label: 'Post Body' + size: '' + span: full + type: richeditor + timestamp: + label: 'UNIX Timestamp' + span: auto + type: number + channel: + label: Channel + nameFrom: title + descriptionFrom: description + span: auto + type: relation diff --git a/www/plugins/jasonwilliams/feed/models/settings/fields.yaml b/www/plugins/jasonwilliams/feed/models/settings/fields.yaml new file mode 100644 index 0000000..7c5293c --- /dev/null +++ b/www/plugins/jasonwilliams/feed/models/settings/fields.yaml @@ -0,0 +1,51 @@ +fields: + twitter_section: + label: Twitter API Settings + type: section + twitter_username: + label: Username + description: Your twitter username, excluding the @ + span: left + twitter_latest_tweet: + label: Latest Tweet ID + description: The ID of the most recent tweet already fetched and stored + span: right + twitter_consumer_key: + label: Consumer Key + description: Your consumer key for accessing the twitter API + span: left + twitter_consumer_secret: + label: Consumer Key Secret + description: Your consumer key secret for accessing the twitter API + span: right + twitter_access_token: + label: Access Token + description: Your access token for accessing the twitter API + span: left + twitter_access_token_secret: + label: Access Token Secret + description: Your access token secret for accessing the twitter API + span: right + + instagram_section: + label: Instagram API Settings + type: section + instagram_access_token: + label: Access Token + description: Your access token for accessing the instagram API + span: full + instagram_latest_post: + label: Latest Post Timestamp + description: The unix timestamp of the most recent post already fetched and stored + span: left + + foursquare_section: + label: Foursquare API Settings + type: section + foursquare_access_token: + description: Your access token for accessing the foursquare API + span: full + foursquare_latest_post: + label: Latest Checkin Timestamp + description: The unix timestamp of the most recent checkin already fetched and stored + span: left diff --git a/www/plugins/jasonwilliams/feed/plugin.yaml b/www/plugins/jasonwilliams/feed/plugin.yaml new file mode 100644 index 0000000..01b072e --- /dev/null +++ b/www/plugins/jasonwilliams/feed/plugin.yaml @@ -0,0 +1,20 @@ +plugin: + name: 'jasonwilliams.feed::lang.plugin.name' + description: 'jasonwilliams.feed::lang.plugin.description' + author: 'Jason Williams' + icon: oc-icon-newspaper-o + homepage: 'https://ja.son-williams.ca/' +navigation: + main-menu-item: + label: Feed + url: jasonwilliams/feed/feed + icon: icon-newspaper-o + sideMenu: + side-menu-item: + label: 'Social Feed' + url: jasonwilliams/feed/feed + icon: icon-sitemap + side-menu-item2: + label: 'Social Channels' + url: jasonwilliams/feed/channels + icon: icon-sitemap diff --git a/www/plugins/jasonwilliams/feed/updates/jasonwilliams_feed_initial_setup.php b/www/plugins/jasonwilliams/feed/updates/jasonwilliams_feed_initial_setup.php new file mode 100644 index 0000000..50e0d95 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/updates/jasonwilliams_feed_initial_setup.php @@ -0,0 +1,57 @@ +engine = 'InnoDB'; + $table->increments('id')->unsigned(); + $table->integer('timestamp')->unsigned(); + $table->integer('channel_id')->unsigned(); + $table->string('title', 512)->nullable(); + $table->string('link', 256)->nullable(); + $table->text('body')->nullable(); + $table->text('extra'); + }); + + Schema::create('jasonwilliams_feed_tags', function($table) + { + $table->engine = 'InnoDB'; + $table->increments('id')->unsigned(); + $table->string('tag', 128); + $table->integer('feed_item_id')->unsigned(); + }); + + Schema::create('jasonwilliams_feed_links', function($table) + { + $table->engine = 'InnoDB'; + $table->increments('id')->unsigned(); + $table->string('addr', 256); + $table->string('display', 256); + $table->integer('feed_item_id')->unsigned(); + }); + + Schema::create('jasonwilliams_feed_channels', function($table) + { + $table->engine = 'InnoDB'; + $table->increments('id')->unsigned(); + $table->string('title', 64); + $table->string('slug', 64); + $table->string('icon', 128)->nullable(); + $table->string('colour', 7)->nullable(); + }); + } + + public function down() + { + Schema::dropIfExists('jasonwilliams_feed_'); + Schema::dropIfExists('jasonwilliams_feed_tags'); + Schema::dropIfExists('jasonwilliams_feed_links'); + Schema::dropIfExists('jasonwilliams_feed_channels'); + } +} diff --git a/www/plugins/jasonwilliams/feed/updates/version.yaml b/www/plugins/jasonwilliams/feed/updates/version.yaml new file mode 100644 index 0000000..dc46b65 --- /dev/null +++ b/www/plugins/jasonwilliams/feed/updates/version.yaml @@ -0,0 +1,3 @@ +1.0.1: + - 'Initialize plugin and create database tables.' + - jasonwilliams_feed_initial_setup.php diff --git a/www/themes/jason-williamsca/assets/css/resume.css b/www/themes/jason-williamsca/assets/css/resume.css index ce75600..c78d2de 100644 --- a/www/themes/jason-williamsca/assets/css/resume.css +++ b/www/themes/jason-williamsca/assets/css/resume.css @@ -1,23 +1,7 @@ -h1.master { - margin: 0; - font-family: var(--title-font); - font-size: 3.5rem; - word-spacing: -0.15em; -} - -h2 { - font-size: 1.5rem; -} - h1 + h2, h2:first-child { margin-top: 0; } -h3 { - font-size: 1rem; - color: var(--dark-alt-accent-color); -} - section h1:first-child { margin-top: 0; } @@ -26,14 +10,6 @@ section + section { margin-top: 3rem; } -.main { - font-size: 1.2rem; -} - -.card { - margin-bottom: 2rem; -} - .card ul { margin: 0; padding-left: 1rem; diff --git a/www/themes/jason-williamsca/assets/css/theme.css b/www/themes/jason-williamsca/assets/css/theme.css index 1755dce..a225392 100644 --- a/www/themes/jason-williamsca/assets/css/theme.css +++ b/www/themes/jason-williamsca/assets/css/theme.css @@ -1,7 +1,7 @@ /* Variables */ :root { - --main-bg-color: #E7D9D1; /*#F0E4D1; #E8D5B5; #e7d9d1;*/ - --main-accent-color: #302b44; + --main-bg-color: #F2EDEA; + --main-accent-color: #302B44; --light-accent-color: #686283; --xlight-accent-color: #88839C; --xxlight-accent-color: #FFFFFF; @@ -87,6 +87,22 @@ footer { } /* Standard element styles */ +h1.master { + margin: 0; + font-family: var(--title-font); + font-size: 3.5rem; + word-spacing: -0.15em; +} + +h2 { + font-size: 1.5rem; +} + +h3 { + font-size: 1rem; + color: var(--dark-alt-accent-color); +} + a { color: var(--alt-accent-color); text-decoration: underline; @@ -98,6 +114,10 @@ a.icon-link { color: var(--accent-color); } +a.btn { + text-decoration: none; +} + a:hover { color: var(--dark-alt-accent-color); text-decoration: none; @@ -142,6 +162,18 @@ html::-webkit-scrollbar-thumb { color: var(--xxlight-accent-color); } +.main, .card p.main, .card ul.main { + font-size: 1.2rem; +} + +.col-container { + margin: 0 -15px; +} + +.card { + margin-bottom: 2rem; +} + .social-icons { font-size: 0; } @@ -177,6 +209,57 @@ html::-webkit-scrollbar-thumb { font-size: 0.9rem; } +.card .feedcard-footer p, .card .feedcard-footer a { + margin-bottom: 0; + font-size: 0.75rem; + color: rgba(0, 0, 0, 0.5); + text-decoration: none; +} + +.card .feedcard-footer img { + height: 0.75rem; +} + +.card .feedcard-footer .feedcard-itemtype { + width: 33%; + text-align: right; +} + +.card .feedcard-footer .feedcard-attribution { + width: 67%; +} + .altaccent { color: var(--alt-accent-color); -} \ No newline at end of file +} + +/* Tag cloud styles */ +div.jqcloud { + font-size: 0.5rem; + line-height: normal; +} + +div.jqcloud a { + color: inherit; + font-size: inherit; + text-decoration: none +} + +div.jqcloud a:hover { + color: var(--dark-alt-accent-color); +} + +div.jqcloud span.w10 {font-size:550%; color: rgba(176, 92, 113, 1);} +div.jqcloud span.w9 {font-size:500%; color: rgba(176, 92, 113, 0.95);} +div.jqcloud span.w8 {font-size:450%; color: rgba(176, 92, 113, 0.9);} +div.jqcloud span.w7 {font-size:400%; color: rgba(176, 92, 113, 0.85);} +div.jqcloud span.w6 {font-size:350%; color: rgba(176, 92, 113, 0.8);} +div.jqcloud span.w5 {font-size:300%; color: rgba(176, 92, 113, 0.75);} +div.jqcloud span.w4 {font-size:250%; color: rgba(176, 92, 113, 0.7);} +div.jqcloud span.w3 {font-size:200%; color: rgba(176, 92, 113, 0.65);} +div.jqcloud span.w2 {font-size:150%; color: rgba(176, 92, 113, 0.6);} +div.jqcloud span.w1 {font-size:100%; color: rgba(176, 92, 113, 0.55);} + +div.jqcloud{overflow:hidden;position:relative} + +div.jqcloud span{padding:0} \ No newline at end of file diff --git a/www/themes/jason-williamsca/assets/images/loading.svg b/www/themes/jason-williamsca/assets/images/loading.svg new file mode 100644 index 0000000..71533d9 --- /dev/null +++ b/www/themes/jason-williamsca/assets/images/loading.svg @@ -0,0 +1 @@ + diff --git a/www/themes/jason-williamsca/assets/javascript/common.js b/www/themes/jason-williamsca/assets/javascript/common.js new file mode 100644 index 0000000..c3f46ea --- /dev/null +++ b/www/themes/jason-williamsca/assets/javascript/common.js @@ -0,0 +1,17 @@ +$(() => { + // Enable any tooltips on the page + $('[data-toggle="tooltip"]').tooltip() + + // Enable masonry layout + $('.masonry').each(function() { + var opts = { itemSelector: '.masonry-item' } + if ($(this).hasClass('masonry-horizontal')) opts.horizontalOrder = true + + $(this).masonry(opts) + }) + + // Resize textareas to fit their content + $('textarea').each(function() { + $(this).height($(this)[0].scrollHeight) + }) +}); \ No newline at end of file diff --git a/www/themes/jason-williamsca/assets/javascript/feedlayout.js b/www/themes/jason-williamsca/assets/javascript/feedlayout.js new file mode 100644 index 0000000..48c3bf3 --- /dev/null +++ b/www/themes/jason-williamsca/assets/javascript/feedlayout.js @@ -0,0 +1,45 @@ +(function($) { + "use strict"; // Start of use strict + + // If the feed is loaded immediadely on page load (i.e. we're not using ajax), reset the layout on each image load + $('.masonry img').each(function() { + $(this).one('load', () => { + $('.masonry').masonry() + }) + }) + + // Apply masonry layout to feed cards after content (including images) has been loaded + $(window).on('feedLoaded', function() { + if (!feedItemClass) feedItemClass = 'col-sm-12 col-md-6 col-xl-4' + + $('.masonry-item').addClass(feedItemClass) + $('.show-onfeedloaded').removeClass('d-none') + + $('.masonry').masonry('reloadItems') + $('.masonry').masonry() + + // Every time an image loads, reset the layout + $('.masonry img').each(function() { + $(this).one('load', () => { + $('.masonry').masonry() + }) + }) + }) + + // If there's tag data set once the page is loaded, create a tag cloud + $(function() { + if(typeof tagData !== 'undefined') { + var cloudWords = [] + + tagData.forEach((tag) => { + cloudWords.push({text: tag.tag, weight: tag.count, link: '/feed/all/' + tag.tag}) + }) + + $('#tagcloud').jQCloud(cloudWords, { + autoResize: true, + removeOverflowing: true, + delay: 2 + }) + } + }) +})(jQuery); // End of use strict \ No newline at end of file diff --git a/www/themes/jason-williamsca/assets/javascript/resume.js b/www/themes/jason-williamsca/assets/javascript/resume.js index ca8e3b2..62fe3f4 100644 --- a/www/themes/jason-williamsca/assets/javascript/resume.js +++ b/www/themes/jason-williamsca/assets/javascript/resume.js @@ -1,23 +1,22 @@ (function($) { "use strict"; // Start of use strict - // Apply masonry layout to cards - $('.masonry').each(function() { - var opts = { itemSelector: '.masonry-wrapper' }; - if ($(this).children().length < 6) opts.horizontalOrder = true; - $(this).masonry(opts); - }); - // Add reorder icon to .card-inline - $('.card-inline').prepend(' '); + $('.card-inline').prepend(' ') - $('.card-inline .inline-icon').click(function() { - var that = $(this); - that.closest('.card').toggleClass('inline').closest('.masonry').masonry(); + // Define a function to toggle the view + const toggleBulletView = function() { + var that = $(this) + that.closest('.card').toggleClass('inline').closest('.masonry').masonry() setTimeout(() => { - that.tooltip('hide'); + that.tooltip('hide') }, 2000); - }); + return false + } + + // Add the function to clicks on the menu button, and/or taps anywhere on the card + $('.card-inline .inline-icon').on('click', toggleBulletView) + $('.card-inline').on('touchstart', toggleBulletView) // Smooth scrolling using jQuery easing $('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function() { diff --git a/www/themes/jason-williamsca/assets/javascript/tooltips.js b/www/themes/jason-williamsca/assets/javascript/tooltips.js deleted file mode 100644 index 7fee50f..0000000 --- a/www/themes/jason-williamsca/assets/javascript/tooltips.js +++ /dev/null @@ -1,3 +0,0 @@ -$(() => { - $('[data-toggle="tooltip"]').tooltip() -}); \ No newline at end of file diff --git a/www/themes/jason-williamsca/layouts/default.htm b/www/themes/jason-williamsca/layouts/default.htm index cc69960..ecc9cf6 100644 --- a/www/themes/jason-williamsca/layouts/default.htm +++ b/www/themes/jason-williamsca/layouts/default.htm @@ -45,6 +45,7 @@ description = "Default Layout"