Adding lazy-load version of feed component to load data using AJAX after page content has been rendered.
This commit is contained in:
parent
2a8454d197
commit
af4f1366a0
39
www/plugins/jasonwilliams/feed/Plugin.php
Normal file
39
www/plugins/jasonwilliams/feed/Plugin.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php namespace jasonwilliams\feed;
|
||||
|
||||
use System\Classes\PluginBase;
|
||||
use JasonWilliams\Feed\Classes\SocialApis;
|
||||
|
||||
class Plugin extends PluginBase
|
||||
{
|
||||
public function registerSettings()
|
||||
{
|
||||
return [
|
||||
'settings' => [
|
||||
'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();
|
||||
}
|
||||
}
|
@ -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') }
|
||||
})
|
||||
})
|
287
www/plugins/jasonwilliams/feed/classes/SocialApis.php
Normal file
287
www/plugins/jasonwilliams/feed/classes/SocialApis.php
Normal file
@ -0,0 +1,287 @@
|
||||
<?php namespace JasonWilliams\Feed\Classes;
|
||||
|
||||
use JasonWilliams\Feed\Models\Settings;
|
||||
use JasonWilliams\Feed\Models\FeedItem;
|
||||
use October\Rain\Network\Http;
|
||||
|
||||
class SocialApis
|
||||
{
|
||||
// Update the database with the latest tweets
|
||||
public static function updateTwitter()
|
||||
{
|
||||
$tweets = self::fetchTwitter();
|
||||
|
||||
if (sizeof($tweets) > 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;
|
||||
}
|
41
www/plugins/jasonwilliams/feed/components/ChannelList.php
Normal file
41
www/plugins/jasonwilliams/feed/components/ChannelList.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php namespace JasonWilliams\Feed\Components;
|
||||
|
||||
use Cms\Classes\ComponentBase;
|
||||
use JasonWilliams\Feed\Models\Channels;
|
||||
|
||||
class ChannelList extends ComponentBase
|
||||
{
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => '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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?php namespace JasonWilliams\Feed\Components;
|
||||
|
||||
use Cms\Classes\ComponentBase;
|
||||
use JasonWilliams\Feed\Models\FeedItem;
|
||||
|
||||
class LazyLoadShortFeed extends ComponentBase
|
||||
{
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => '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();
|
||||
}
|
||||
}
|
79
www/plugins/jasonwilliams/feed/components/ShortFeed.php
Normal file
79
www/plugins/jasonwilliams/feed/components/ShortFeed.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php namespace JasonWilliams\Feed\Components;
|
||||
|
||||
use Cms\Classes\ComponentBase;
|
||||
use JasonWilliams\Feed\Models\FeedItem;
|
||||
use JasonWilliams\Feed\Models\Tags;
|
||||
|
||||
class ShortFeed extends ComponentBase
|
||||
{
|
||||
private $channelfilter;
|
||||
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => '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();
|
||||
}
|
||||
}
|
26
www/plugins/jasonwilliams/feed/components/TagList.php
Normal file
26
www/plugins/jasonwilliams/feed/components/TagList.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php namespace JasonWilliams\Feed\Components;
|
||||
|
||||
use Db;
|
||||
use Cms\Classes\ComponentBase;
|
||||
use JasonWilliams\Feed\Models\Tags;
|
||||
|
||||
class TagList extends ComponentBase
|
||||
{
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => '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();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
{% for channel in channels %}
|
||||
<li><a href="/feed/{{ channel.slug }}">{{ channel.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
@ -0,0 +1,4 @@
|
||||
<div id="feedarea">Loading...</div>
|
||||
{% put scripts %}
|
||||
<script type="text/javascript">const renderPartial = '{{ renderPartial }}'</script>
|
||||
{% endput %}
|
@ -0,0 +1,9 @@
|
||||
<ul>
|
||||
{% for post in posts %}
|
||||
{% if post.title %}
|
||||
<li>{{ post.title | raw }}</li>
|
||||
{% else %}
|
||||
<li>Post</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
@ -0,0 +1,9 @@
|
||||
<ul>
|
||||
{% for post in posts %}
|
||||
{% if post.title %}
|
||||
<li>{{ post.title | raw }}</li>
|
||||
{% else %}
|
||||
<li>Post</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
{% for tag in tags %}
|
||||
<li><a href="{{ tag.tag }}">{{ tag.tag }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
19
www/plugins/jasonwilliams/feed/controllers/Channels.php
Normal file
19
www/plugins/jasonwilliams/feed/controllers/Channels.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php namespace jasonwilliams\feed\Controllers;
|
||||
|
||||
use Backend\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
class Channels extends Controller
|
||||
{
|
||||
public $implement = [ 'Backend\Behaviors\ListController', 'Backend\Behaviors\FormController', 'Backend\Behaviors\ReorderController' ];
|
||||
|
||||
public $listConfig = 'config_list.yaml';
|
||||
public $formConfig = 'config_form.yaml';
|
||||
public $reorderConfig = 'config_reorder.yaml';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
BackendMenu::setContext('jasonwilliams.feed', 'main-menu-item');
|
||||
}
|
||||
}
|
19
www/plugins/jasonwilliams/feed/controllers/Feed.php
Normal file
19
www/plugins/jasonwilliams/feed/controllers/Feed.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php namespace jasonwilliams\feed\Controllers;
|
||||
|
||||
use Backend\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
class Feed extends Controller
|
||||
{
|
||||
public $implement = [ 'Backend\Behaviors\ListController', 'Backend\Behaviors\FormController', 'Backend\Behaviors\ReorderController' ];
|
||||
|
||||
public $listConfig = 'config_list.yaml';
|
||||
public $formConfig = 'config_form.yaml';
|
||||
public $reorderConfig = 'config_reorder.yaml';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
BackendMenu::setContext('jasonwilliams.feed', 'main-menu-item');
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/channels/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/channels/reorder') ?>" class="btn btn-default oc-icon-list"><?= e(trans('backend::lang.reorder.default_title')) ?></a>
|
||||
<button
|
||||
class="btn btn-default oc-icon-trash-o"
|
||||
disabled="disabled"
|
||||
onclick="$(this).data('request-data', {
|
||||
checked: $('.control-list').listWidget('getChecked')
|
||||
})"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
|
||||
data-trigger-action="enable"
|
||||
data-trigger=".control-list input[type=checkbox]"
|
||||
data-trigger-condition="checked"
|
||||
data-request-success="$(this).prop('disabled', true)"
|
||||
data-stripe-load-indicator>
|
||||
<?= e(trans('backend::lang.list.delete_selected')) ?>
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/channels') ?>" class="btn btn-primary oc-icon-caret-left"><?= e(trans('backend::lang.form.return_to_list')) ?></a>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
name: Channels
|
||||
form: $/jasonwilliams/feed/models/channels/fields.yaml
|
||||
modelClass: jasonwilliams\feed\Models\Channels
|
||||
defaultRedirect: jasonwilliams/feed/channels
|
||||
create:
|
||||
redirect: 'jasonwilliams/feed/channels/update/:id'
|
||||
redirectClose: jasonwilliams/feed/channels
|
||||
update:
|
||||
redirect: jasonwilliams/feed/channels
|
||||
redirectClose: jasonwilliams/feed/channels
|
@ -0,0 +1,12 @@
|
||||
title: Channels
|
||||
modelClass: jasonwilliams\feed\Models\Channels
|
||||
list: $/jasonwilliams/feed/models/channels/columns.yaml
|
||||
recordUrl: 'jasonwilliams/feed/channels/update/:id'
|
||||
noRecordsMessage: 'backend::lang.list.no_records'
|
||||
recordsPerPage: 20
|
||||
showSetup: true
|
||||
showCheckboxes: true
|
||||
toolbar:
|
||||
buttons: list_toolbar
|
||||
search:
|
||||
prompt: 'backend::lang.list.search_prompt'
|
@ -0,0 +1,4 @@
|
||||
title: Channels
|
||||
modelClass: jasonwilliams\feed\Models\Channels
|
||||
toolbar:
|
||||
buttons: reorder_toolbar
|
@ -0,0 +1,46 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/channels') ?>">Channels</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.create')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.create_and_close')) ?>
|
||||
</button>
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('jasonwilliams/feed/channels') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('jasonwilliams/feed/channels') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
|
||||
<?php endif ?>
|
@ -0,0 +1 @@
|
||||
<?= $this->listRender() ?>
|
@ -0,0 +1,22 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/channels') ?>">Channels</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<div class="form-preview">
|
||||
<?= $this->formRenderPreview() ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<p>
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/channels') ?>" class="btn btn-default oc-icon-chevron-left">
|
||||
<?= e(trans('backend::lang.form.return_to_list')) ?>
|
||||
</a>
|
||||
</p>
|
@ -0,0 +1,8 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/channels') ?>">Channels</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?= $this->reorderRender() ?>
|
@ -0,0 +1,54 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/channels') ?>">Channels</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-request-data="redirect:0"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.save')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.save_and_close')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="oc-icon-trash-o btn-icon danger pull-right"
|
||||
data-request="onDelete"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.deleting')) ?>"
|
||||
data-request-confirm="<?= e(trans('backend::lang.form.confirm_delete')) ?>">
|
||||
</button>
|
||||
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('jasonwilliams/feed/channels') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('jasonwilliams/feed/channels') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
|
||||
<?php endif ?>
|
@ -0,0 +1,19 @@
|
||||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/feed/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/feed/reorder') ?>" class="btn btn-default oc-icon-list"><?= e(trans('backend::lang.reorder.default_title')) ?></a>
|
||||
<button
|
||||
class="btn btn-default oc-icon-trash-o"
|
||||
disabled="disabled"
|
||||
onclick="$(this).data('request-data', {
|
||||
checked: $('.control-list').listWidget('getChecked')
|
||||
})"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
|
||||
data-trigger-action="enable"
|
||||
data-trigger=".control-list input[type=checkbox]"
|
||||
data-trigger-condition="checked"
|
||||
data-request-success="$(this).prop('disabled', true)"
|
||||
data-stripe-load-indicator>
|
||||
<?= e(trans('backend::lang.list.delete_selected')) ?>
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/feed') ?>" class="btn btn-primary oc-icon-caret-left"><?= e(trans('backend::lang.form.return_to_list')) ?></a>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
name: Feed
|
||||
form: $/jasonwilliams/feed/models/feeditem/fields.yaml
|
||||
modelClass: jasonwilliams\feed\Models\FeedItem
|
||||
defaultRedirect: jasonwilliams/feed/feed
|
||||
create:
|
||||
redirect: 'jasonwilliams/feed/feed/update/:id'
|
||||
redirectClose: jasonwilliams/feed/feed
|
||||
update:
|
||||
redirect: jasonwilliams/feed/feed
|
||||
redirectClose: jasonwilliams/feed/feed
|
@ -0,0 +1,12 @@
|
||||
title: Feed
|
||||
modelClass: jasonwilliams\feed\Models\FeedItem
|
||||
list: $/jasonwilliams/feed/models/feeditem/columns.yaml
|
||||
recordUrl: 'jasonwilliams/feed/feed/update/:id'
|
||||
noRecordsMessage: 'backend::lang.list.no_records'
|
||||
recordsPerPage: 20
|
||||
showSetup: true
|
||||
showCheckboxes: true
|
||||
toolbar:
|
||||
buttons: list_toolbar
|
||||
search:
|
||||
prompt: 'backend::lang.list.search_prompt'
|
@ -0,0 +1,4 @@
|
||||
title: Feed
|
||||
modelClass: jasonwilliams\feed\Models\FeedItem
|
||||
toolbar:
|
||||
buttons: reorder_toolbar
|
46
www/plugins/jasonwilliams/feed/controllers/feed/create.htm
Normal file
46
www/plugins/jasonwilliams/feed/controllers/feed/create.htm
Normal file
@ -0,0 +1,46 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/feed') ?>">Feed</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.create')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.create_and_close')) ?>
|
||||
</button>
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('jasonwilliams/feed/feed') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('jasonwilliams/feed/feed') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
|
||||
<?php endif ?>
|
@ -0,0 +1 @@
|
||||
<?= $this->listRender() ?>
|
22
www/plugins/jasonwilliams/feed/controllers/feed/preview.htm
Normal file
22
www/plugins/jasonwilliams/feed/controllers/feed/preview.htm
Normal file
@ -0,0 +1,22 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/feed') ?>">Feed</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<div class="form-preview">
|
||||
<?= $this->formRenderPreview() ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<p>
|
||||
<a href="<?= Backend::url('jasonwilliams/feed/feed') ?>" class="btn btn-default oc-icon-chevron-left">
|
||||
<?= e(trans('backend::lang.form.return_to_list')) ?>
|
||||
</a>
|
||||
</p>
|
@ -0,0 +1,8 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/feed') ?>">Feed</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?= $this->reorderRender() ?>
|
54
www/plugins/jasonwilliams/feed/controllers/feed/update.htm
Normal file
54
www/plugins/jasonwilliams/feed/controllers/feed/update.htm
Normal file
@ -0,0 +1,54 @@
|
||||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('jasonwilliams/feed/feed') ?>">Feed</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-request-data="redirect:0"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.save')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.save_and_close')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="oc-icon-trash-o btn-icon danger pull-right"
|
||||
data-request="onDelete"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.deleting')) ?>"
|
||||
data-request-confirm="<?= e(trans('backend::lang.form.confirm_delete')) ?>">
|
||||
</button>
|
||||
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('jasonwilliams/feed/feed') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('jasonwilliams/feed/feed') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
|
||||
<?php endif ?>
|
6
www/plugins/jasonwilliams/feed/lang/en-ca/lang.php
Normal file
6
www/plugins/jasonwilliams/feed/lang/en-ca/lang.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php return [
|
||||
'plugin' => [
|
||||
'name' => 'Social Feed',
|
||||
'description' => 'Captures social media posts from a variety of services and consolidates them into a single feed.'
|
||||
]
|
||||
];
|
33
www/plugins/jasonwilliams/feed/models/Channels.php
Normal file
33
www/plugins/jasonwilliams/feed/models/Channels.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php namespace jasonwilliams\feed\Models;
|
||||
|
||||
use Model;
|
||||
|
||||
/**
|
||||
* Model
|
||||
*/
|
||||
class Channels extends Model
|
||||
{
|
||||
use \October\Rain\Database\Traits\Validation;
|
||||
|
||||
/*
|
||||
* Disable timestamps by default.
|
||||
* Remove this line if timestamps are defined in the database table.
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'jasonwilliams_feed_channels';
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
];
|
||||
|
||||
public $hasMany = [
|
||||
'feeditems' => ['jasonwilliams\feed\Models\FeedItem', 'key' => 'channel_id']
|
||||
];
|
||||
}
|
65
www/plugins/jasonwilliams/feed/models/FeedItem.php
Normal file
65
www/plugins/jasonwilliams/feed/models/FeedItem.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php namespace jasonwilliams\feed\Models;
|
||||
|
||||
use Model;
|
||||
|
||||
/**
|
||||
* Model
|
||||
*/
|
||||
class FeedItem extends Model
|
||||
{
|
||||
use \October\Rain\Database\Traits\Validation;
|
||||
|
||||
/*
|
||||
* Disable timestamps by default.
|
||||
* Remove this line if timestamps are defined in the database table.
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'jasonwilliams_feed_';
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
];
|
||||
|
||||
public $hasMany = [
|
||||
'links' => '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*)/', '<a href="/feed/all/$1">#$1</a>', $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_]+)/', '<a href="http://twitter.com/$1">@$1</a>', $this->title);
|
||||
|
||||
// Add instagram user links
|
||||
if ($this->channel_id == 3) $this->title = preg_replace('/@([a-zA-Z0-9_]+)/', '<a href="https://www.instagram.com/$1">@$1</a>', $this->title);
|
||||
|
||||
// Add foursquare user Links
|
||||
if ($this->channel_id == 6 && !empty($this->body)) $this->body = preg_replace('/@([a-zA-Z0-9_]+)/', '<a href="https://www.foursquare.com/$1">@$1</a>', $this->body);
|
||||
|
||||
// Find and replace links
|
||||
foreach($this->links as $link)
|
||||
{
|
||||
$this->title = str_replace($link->addr, "<a href=\"".$link->addr."\">".$link->display."</a>", $this->title);
|
||||
}
|
||||
}
|
||||
}
|
35
www/plugins/jasonwilliams/feed/models/Links.php
Normal file
35
www/plugins/jasonwilliams/feed/models/Links.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php namespace jasonwilliams\feed\Models;
|
||||
|
||||
use Model;
|
||||
|
||||
/**
|
||||
* Model
|
||||
*/
|
||||
class Links extends Model
|
||||
{
|
||||
use \October\Rain\Database\Traits\Validation;
|
||||
|
||||
/*
|
||||
* Disable timestamps by default.
|
||||
* Remove this line if timestamps are defined in the database table.
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'jasonwilliams_feed_links';
|
||||
|
||||
protected $fillable = ['addr', 'display'];
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
];
|
||||
|
||||
public $belongsTo = [
|
||||
'links' => 'jasonwilliams\feed\Models\FeedItem'
|
||||
];
|
||||
}
|
14
www/plugins/jasonwilliams/feed/models/Settings.php
Normal file
14
www/plugins/jasonwilliams/feed/models/Settings.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php namespace JasonWilliams\Feed\Models;
|
||||
|
||||
use Model;
|
||||
|
||||
class Settings extends Model
|
||||
{
|
||||
public $implement = ['System.Behaviors.SettingsModel'];
|
||||
|
||||
// A unique code
|
||||
public $settingsCode = 'jasonwilliams_feed_settings';
|
||||
|
||||
// Reference to field configuration
|
||||
public $settingsFields = 'fields.yaml';
|
||||
}
|
40
www/plugins/jasonwilliams/feed/models/Tags.php
Normal file
40
www/plugins/jasonwilliams/feed/models/Tags.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php namespace jasonwilliams\feed\Models;
|
||||
|
||||
use Model;
|
||||
|
||||
/**
|
||||
* Model
|
||||
*/
|
||||
class Tags extends Model
|
||||
{
|
||||
use \October\Rain\Database\Traits\Validation;
|
||||
|
||||
/*
|
||||
* Disable timestamps by default.
|
||||
* Remove this line if timestamps are defined in the database table.
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'jasonwilliams_feed_tags';
|
||||
|
||||
protected $fillable = ['tag'];
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
];
|
||||
|
||||
public $belongsTo = [
|
||||
'feeditem' => ['jasonwilliams\feed\Models\FeedItem', 'key' => 'feed_item_id']
|
||||
];
|
||||
|
||||
public function afterFetch()
|
||||
{
|
||||
//dd($this->feeditem);
|
||||
}
|
||||
}
|
10
www/plugins/jasonwilliams/feed/models/channels/columns.yaml
Normal file
10
www/plugins/jasonwilliams/feed/models/channels/columns.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
columns:
|
||||
id:
|
||||
label: 'Channel ID'
|
||||
type: number
|
||||
sortable: true
|
||||
title:
|
||||
label: Title
|
||||
type: text
|
||||
searchable: true
|
||||
sortable: false
|
21
www/plugins/jasonwilliams/feed/models/channels/fields.yaml
Normal file
21
www/plugins/jasonwilliams/feed/models/channels/fields.yaml
Normal file
@ -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
|
@ -0,0 +1,9 @@
|
||||
columns:
|
||||
title:
|
||||
label: 'Post Title'
|
||||
type: text
|
||||
searchable: true
|
||||
timestamp:
|
||||
label: 'UNIX Timestamp'
|
||||
type: number
|
||||
sortable: true
|
20
www/plugins/jasonwilliams/feed/models/feeditem/fields.yaml
Normal file
20
www/plugins/jasonwilliams/feed/models/feeditem/fields.yaml
Normal file
@ -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
|
51
www/plugins/jasonwilliams/feed/models/settings/fields.yaml
Normal file
51
www/plugins/jasonwilliams/feed/models/settings/fields.yaml
Normal file
@ -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
|
20
www/plugins/jasonwilliams/feed/plugin.yaml
Normal file
20
www/plugins/jasonwilliams/feed/plugin.yaml
Normal file
@ -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
|
@ -0,0 +1,57 @@
|
||||
<?php namespace jasonwilliams\feed\Updates;
|
||||
|
||||
use Schema;
|
||||
use October\Rain\Database\Updates\Migration;
|
||||
|
||||
class JasonwilliamsFeedInitialSetup extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('jasonwilliams_feed_', function($table)
|
||||
{
|
||||
$table->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');
|
||||
}
|
||||
}
|
3
www/plugins/jasonwilliams/feed/updates/version.yaml
Normal file
3
www/plugins/jasonwilliams/feed/updates/version.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
1.0.1:
|
||||
- 'Initialize plugin and create database tables.'
|
||||
- jasonwilliams_feed_initial_setup.php
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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}
|
1
www/themes/jason-williamsca/assets/images/loading.svg
Normal file
1
www/themes/jason-williamsca/assets/images/loading.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" width="160px" height="20px" viewBox="0 0 128 16" xml:space="preserve"><path fill="#a8a6b1" fill-opacity="0.42" d="M6.4,4.8A3.2,3.2,0,1,1,3.2,8,3.2,3.2,0,0,1,6.4,4.8Zm12.8,0A3.2,3.2,0,1,1,16,8,3.2,3.2,0,0,1,19.2,4.8ZM32,4.8A3.2,3.2,0,1,1,28.8,8,3.2,3.2,0,0,1,32,4.8Zm12.8,0A3.2,3.2,0,1,1,41.6,8,3.2,3.2,0,0,1,44.8,4.8Zm12.8,0A3.2,3.2,0,1,1,54.4,8,3.2,3.2,0,0,1,57.6,4.8Zm12.8,0A3.2,3.2,0,1,1,67.2,8,3.2,3.2,0,0,1,70.4,4.8Zm12.8,0A3.2,3.2,0,1,1,80,8,3.2,3.2,0,0,1,83.2,4.8ZM96,4.8A3.2,3.2,0,1,1,92.8,8,3.2,3.2,0,0,1,96,4.8Zm12.8,0A3.2,3.2,0,1,1,105.6,8,3.2,3.2,0,0,1,108.8,4.8Zm12.8,0A3.2,3.2,0,1,1,118.4,8,3.2,3.2,0,0,1,121.6,4.8Z"/><g><path fill="#302b44" fill-opacity="1" d="M-42.7,3.84A4.16,4.16,0,0,1-38.54,8a4.16,4.16,0,0,1-4.16,4.16A4.16,4.16,0,0,1-46.86,8,4.16,4.16,0,0,1-42.7,3.84Zm12.8-.64A4.8,4.8,0,0,1-25.1,8a4.8,4.8,0,0,1-4.8,4.8A4.8,4.8,0,0,1-34.7,8,4.8,4.8,0,0,1-29.9,3.2Zm12.8-.64A5.44,5.44,0,0,1-11.66,8a5.44,5.44,0,0,1-5.44,5.44A5.44,5.44,0,0,1-22.54,8,5.44,5.44,0,0,1-17.1,2.56Z"/><animateTransform attributeName="transform" type="translate" values="23 0;36 0;49 0;62 0;74.5 0;87.5 0;100 0;113 0;125.5 0;138.5 0;151.5 0;164.5 0;178 0" calcMode="discrete" dur="1170ms" repeatCount="indefinite"/></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
17
www/themes/jason-williamsca/assets/javascript/common.js
Normal file
17
www/themes/jason-williamsca/assets/javascript/common.js
Normal file
@ -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)
|
||||
})
|
||||
});
|
45
www/themes/jason-williamsca/assets/javascript/feedlayout.js
Normal file
45
www/themes/jason-williamsca/assets/javascript/feedlayout.js
Normal file
@ -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
|
@ -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('<div class="inline-icon" data-toggle="tooltip" title="Toggle bullet view"><i class="fas fa-stream"></i></div>');
|
||||
$('.card-inline').prepend('<div class="inline-icon" data-toggle="tooltip" title="Toggle bullet view"><i class="fas fa-stream"></i></div>')
|
||||
|
||||
$('.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() {
|
||||
|
@ -1,3 +0,0 @@
|
||||
$(() => {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
});
|
@ -45,6 +45,7 @@ description = "Default Layout"
|
||||
<li><a href="/resume">Résumé</a></li>
|
||||
<li><a href="/feed">Feed</a></li>
|
||||
<li><a href="/contact">Contact Me</a></li>
|
||||
<li><a href="/pgp">PGP Public Key</a></li>
|
||||
</ul>
|
||||
{% component 'CurrentInfo' %}
|
||||
<p>Made with <i class="fas fa-heart"></i> in #YYC<br><a href="https://www.madeinyyc.com/"><img src="{{ 'assets/images/madeinyyc.png'|theme }}"></a></p>
|
||||
@ -63,6 +64,8 @@ description = "Default Layout"
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha256-OFRAJNoaD8L3Br5lglV7VyLRf0itmoBzWUoM+Sji4/8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js" integrity="sha256-KzZiKy0DWYsnwMF+X1DvQngQ2/FxF7MF3Ff72XcpuPs=" crossorigin="anonymous"></script>
|
||||
{% framework extras %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js"></script>
|
||||
<script src="{{ 'assets/javascript/common.js' | theme }}"></script>
|
||||
{% scripts %}
|
||||
</body>
|
||||
</html>
|
@ -43,6 +43,8 @@ description = "Page with a Submenu"
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha256-OFRAJNoaD8L3Br5lglV7VyLRf0itmoBzWUoM+Sji4/8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js" integrity="sha256-KzZiKy0DWYsnwMF+X1DvQngQ2/FxF7MF3Ff72XcpuPs=" crossorigin="anonymous"></script>
|
||||
{% framework extras %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js"></script>
|
||||
<script src="{{ 'assets/javascript/common.js' | theme }}"></script>
|
||||
{% scripts %}
|
||||
</body>
|
||||
</html>
|
@ -28,18 +28,22 @@ emails_date_format = "Y-m-d"
|
||||
<div class="col-lg-7">
|
||||
<h1>Contact Me</h1>
|
||||
<p>The simplest and quickest way to contact me is by completing the form below.</p>
|
||||
{% component 'contactform' %}
|
||||
{% component 'contactform' %}
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2>Old School</h2>
|
||||
<p>Sometimes the more traditional ways of getting in touch are just better...</p>
|
||||
<p>IMG</p>
|
||||
<p>4<sup>th</sup> Floor, 1207 11 Ave SW,<br>
|
||||
Calgary, AB, T3C 0M5</p>
|
||||
<p><img style="width: 100%;" src="https://api.mapbox.com/styles/v1/jason32dev/ckgehopjc2o1y19o09aqs4x11/static/pin-l+B05C71(-113.9771279,51.1112454)/-113.9771279,51.1112454,14,0/545x150@2x?access_token=pk.eyJ1IjoiamFzb24zMmRldiIsImEiOiJjazFsNHd5djcwMXptM2htbW8zM3MyZGxuIn0.-TtNNGFysQPQRfGR1P8DUA"></p>
|
||||
<p>3699 63 Ave NW,<br>
|
||||
Calgary, AB, T3J 0G7</p>
|
||||
<p><a href="tel:+18252575025">(825) 257-5025</a></p>
|
||||
|
||||
<h2>New Cool</h2>
|
||||
<p>...and sometimes it's better to keep up with the times.</p>
|
||||
{% partial 'social' nocode = true nocontact = true %}
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<h2>Encryption</h2>
|
||||
<p>If you wish, you may use my <a href="/pgp"><i class="fas fa-key"></i> PGP public key</a> to encrypt anything that you send me.</p>
|
||||
</div>
|
||||
</div>
|
@ -1,6 +1,43 @@
|
||||
title = "Feed"
|
||||
url = "/feed"
|
||||
url = "/feed/:channel?/:tag?/"
|
||||
layout = "default"
|
||||
is_hidden = 0
|
||||
|
||||
[ShortFeed]
|
||||
channelFilter = "{{ :channel }}"
|
||||
tagFilter = "{{ :tag }}"
|
||||
maxItems = 50
|
||||
|
||||
[ChannelList]
|
||||
includeEmpty = 0
|
||||
|
||||
[TagList]
|
||||
==
|
||||
<p>Feed</p>
|
||||
<?php
|
||||
function onStart()
|
||||
{
|
||||
$this->addJs('https://cdnjs.cloudflare.com/ajax/libs/jqcloud/1.0.3/jqcloud.min.js');
|
||||
$this->addJs('assets/javascript/feedlayout.js');
|
||||
//$this->addCss('https://cdnjs.cloudflare.com/ajax/libs/jqcloud/1.0.3/jqcloud.min.css');
|
||||
}
|
||||
?>
|
||||
==
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<h1>Feed</h1>
|
||||
<div id="feedarea" class="col-container masonry">
|
||||
{% partial 'feed/template' feeditemclass='col-sm-12 col-md-6 col-xl-4' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<h2>Channels</h2>
|
||||
{% component 'ChannelList' %}
|
||||
|
||||
<h2>Tags</h2>
|
||||
<div id="tagcloud" style="height: 500px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% put scripts %}
|
||||
<script type="text/javascript">const tagData = {{ tags | raw }};</script>
|
||||
{% endput %}
|
29
www/themes/jason-williamsca/pages/maintenance.htm
Normal file
29
www/themes/jason-williamsca/pages/maintenance.htm
Normal file
@ -0,0 +1,29 @@
|
||||
title = "ja.son-williams.ca"
|
||||
url = "/maintenance"
|
||||
is_hidden = 0
|
||||
==
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>son-williams.ca</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro|Merriweather:300italic,300' rel='stylesheet' type='text/css'>
|
||||
<style type="text/css">
|
||||
* {margin: 0; padding: 0; box-sizing: border-box;}
|
||||
html, body {height: 100%; color: #000000;}
|
||||
#view {height: 100%; width: 100%; overflow: hidden;}
|
||||
#verticalcenter {margin: 0; position: absolute; top: 50%; width: 100%; transform: translateY(-50%);}
|
||||
#verticalcenter p {font-family: 'Merriweather', serif; text-align: center; font-size: 24px; font-weight: 300; font-style: italic;}
|
||||
#view p#pilink {position: absolute; bottom: 20px; right: 20px; font-size: 20px;}
|
||||
#view p#pilink a {text-decoration: none; color: #BBC3D0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="view">
|
||||
<div id="verticalcenter">
|
||||
<p>ja.son-williams.ca</p>
|
||||
</div>
|
||||
<p id="pilink"><a href="/backend">π</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
17
www/themes/jason-williamsca/pages/pgp-public-key.htm
Normal file
17
www/themes/jason-williamsca/pages/pgp-public-key.htm
Normal file
@ -0,0 +1,17 @@
|
||||
title = "PGP Public Key"
|
||||
url = "/pgp"
|
||||
layout = "default"
|
||||
is_hidden = 0
|
||||
==
|
||||
<div class="row">
|
||||
<div class="col-lg-7">
|
||||
<h1>PGP Public Key</h1>
|
||||
{% partial 'pgpkey' %}
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2>About</h2>
|
||||
<p>If you wish, you may use my PGP public key here to encrypt anything that you send me. Of course I'll need a way to get my hands on your public key in order to be able to decrypt it, so please make sure you provide that or that you've published it to a well-known public directory like <a href="https://keyserver.pgp.com">keyserver.pgp.com</a>.</p>
|
||||
<h2>Contact Me</h2>
|
||||
{% partial 'social' noinfrequent = true nocode = true %}
|
||||
</div>
|
||||
</div>
|
@ -9,7 +9,6 @@ function onStart()
|
||||
$this['sideMenu'] = 'resumemenu';
|
||||
$this->addCss('assets/css/resume.css');
|
||||
$this->addJs('https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js');
|
||||
$this->addJs('https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js');
|
||||
$this->addJs('assets/javascript/resume.js');
|
||||
}
|
||||
?>
|
||||
@ -31,8 +30,8 @@ function onStart()
|
||||
<section class="resume-section d-flex align-items-center" id="skills">
|
||||
<div class="w-100">
|
||||
<h1>Skills & Expertise</h1>
|
||||
<div class="masonry">
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="col-container masonry masonry-horizontal">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card card-inline inline">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Product Management</h2>
|
||||
@ -49,7 +48,7 @@ function onStart()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card card-inline inline">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Project Management</h2>
|
||||
@ -69,7 +68,7 @@ function onStart()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card card-inline inline">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Lean Six-Sigma</h2>
|
||||
@ -86,7 +85,7 @@ function onStart()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card card-inline inline">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Business Analysis</h2>
|
||||
@ -103,7 +102,7 @@ function onStart()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card card-inline inline">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Business Technology</h2>
|
||||
@ -162,7 +161,7 @@ function onStart()
|
||||
<p>My primary focus was leading projects within the retail arm of the bank, I also acted as a single point of contact representing the retail banking business unit on large-scale, enterprise wide initiatives, playing a leading role in stakeholder, resource and organizational change management.</p>
|
||||
</div>
|
||||
<div class="resume-date text-md-right">
|
||||
<span class="text-primary">March 2013 - September 2017</span>
|
||||
<span class="text-primary">November 2014 - September 2017</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -185,7 +184,7 @@ function onStart()
|
||||
<p>Acted as the primary point of contact for small and medium business customers based throughout the UK and beyond, providing banking support and services. Assisted with transactional queries both simple and complex. Sold and advised clients on a wide variety of products and services. Demonstrated a commitment to providing industry leading levels of customer service, and received multiple individual awards for excellent customer satisfaction scores.</p>
|
||||
</div>
|
||||
<div class="resume-date text-md-right">
|
||||
<span class="text-primary">March 2013 - Present</span>
|
||||
<span class="text-primary">March 2010 - July 2010</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -263,10 +262,10 @@ function onStart()
|
||||
<section class="resume-section d-flex align-items-center" id="press">
|
||||
<div class="w-100">
|
||||
<h1>Press</h1>
|
||||
<div class="masonry" data-blah="blah">
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="col-container masonry masonry-horizontal">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card">
|
||||
<img class="card-img-top" src="{{ 'istock-1051617224-100798650-large.jpg'|media }}">
|
||||
<img class="card-img-top" src="{{ 'istock-1051617224-100798650-large.jpg' | media }}">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Connected RPA Delivers A Digital Workforce</h2>
|
||||
<h3>Computerworld · June 2019</h3>
|
||||
@ -276,9 +275,9 @@ function onStart()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card">
|
||||
<img class="card-img-top" src="{{ 'disrupting-industry.png'|media }}">
|
||||
<img class="card-img-top" src="{{ 'disrupting-industry.png' | media }}">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">This Canadian financial institution disrupted their industry with AI!</h2>
|
||||
<h3>AI Week · August 2018</h3>
|
||||
@ -288,9 +287,9 @@ function onStart()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card">
|
||||
<img class="card-img-top" src="{{ 'atb-financial-g-suite.jpg'|media }}">
|
||||
<img class="card-img-top" src="{{ 'atb-financial-g-suite.jpg' | media }}">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Why ATB Financial moved its entire organization to G Suite</h2>
|
||||
<h3>TechRepublic · March 2018</h3>
|
||||
@ -300,9 +299,9 @@ function onStart()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="masonry-wrapper col-sm-6 col-xl-4">
|
||||
<div class="masonry-item col-sm-6 col-xl-4">
|
||||
<div class="card">
|
||||
<img class="card-img-top" src="{{ 'bryan-kenny.jpg'|media }}">
|
||||
<img class="card-img-top" src="{{ 'bryan-kenny.jpg' | media }}">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">High-tech bank helps Edmonton's homeless access financial services</h2>
|
||||
<h3>CBC · September 2017</h3>
|
||||
|
6
www/themes/jason-williamsca/pages/shrapnel.htm
Normal file
6
www/themes/jason-williamsca/pages/shrapnel.htm
Normal file
@ -0,0 +1,6 @@
|
||||
title = "Shrapnel"
|
||||
url = "/feed/shrapnel/:post/"
|
||||
layout = "default"
|
||||
is_hidden = 0
|
||||
==
|
||||
<h1>Shrapnel Post</h1>
|
8
www/themes/jason-williamsca/pages/test.htm
Normal file
8
www/themes/jason-williamsca/pages/test.htm
Normal file
@ -0,0 +1,8 @@
|
||||
title = "Test"
|
||||
url = "/test"
|
||||
is_hidden = 0
|
||||
|
||||
[ShortFeed]
|
||||
maxItems = 10
|
||||
==
|
||||
{% component 'ShortFeed' %}
|
15
www/themes/jason-williamsca/pages/uh-oh.htm
Normal file
15
www/themes/jason-williamsca/pages/uh-oh.htm
Normal file
@ -0,0 +1,15 @@
|
||||
title = "Uh oh"
|
||||
url = "/404"
|
||||
layout = "default"
|
||||
is_hidden = 0
|
||||
==
|
||||
<div class="row">
|
||||
<div class="col-lg-7">
|
||||
<h1>Uh oh</h1>
|
||||
<iframe width="100%" height="300" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/412499226&color=%23ff5500&auto_play=true&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true"></iframe>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<p style="margin-top: 6rem;">Well this is embarassing for one of us. The page you asked for is nowhere to be found. Bad link? Mistyped address? I'm not sure.</p>
|
||||
<p>There's not really all that much stuff here, so you could always try making a selection from the menu at the top right of the page. If you think something's wrong, <a href="/contact">contact me</a>.
|
||||
</div>
|
||||
</div>
|
@ -2,6 +2,17 @@ title = "Welcome"
|
||||
url = "/"
|
||||
layout = "default"
|
||||
is_hidden = 0
|
||||
|
||||
[LazyLoadShortFeed]
|
||||
maxItems = 5
|
||||
renderPartial = "feed/template"
|
||||
==
|
||||
<?php
|
||||
function onStart()
|
||||
{
|
||||
$this->addJs('assets/javascript/feedlayout.js');
|
||||
}
|
||||
?>
|
||||
==
|
||||
<div class="row">
|
||||
<div class="col-lg-5 order-lg-2">
|
||||
@ -15,5 +26,7 @@ is_hidden = 0
|
||||
</div>
|
||||
<div class="col-lg-7 order-lg-1">
|
||||
<h1>Feed</h1>
|
||||
{% partial 'feed/placeholder' feeditemclass='col-sm-12 col-md-6' %}
|
||||
<a class="btn btn-outline-primary d-none show-onfeedloaded" href="/feed" role="button">View More...</a>
|
||||
</div>
|
||||
</div>
|
@ -7,24 +7,14 @@ description = "Contact Me Form"
|
||||
<div id="{{ __SELF__ }}_forms_flash"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<!--<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" class="form-control">-->
|
||||
<input type="text" id="name" name="name" class="form-control" placeholder="Your Name" required autofocus>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<!--<label for="email">Email:</label>
|
||||
<input type="text" id="email" name="email" class="form-control">-->
|
||||
<input type="email" id="email" name="email" class="form-control" placeholder="Email Address" required>
|
||||
</div>
|
||||
|
||||
<!--<div class="form-group">
|
||||
<label for="subject">Subject:</label>
|
||||
<input type="text" id="subject" name="subject" class="form-control">
|
||||
</div>-->
|
||||
|
||||
<div class="form-group">
|
||||
<!--<label for="message">Message:</label>-->
|
||||
<textarea id="message" name="message" rows="8" class="form-control" placeholder="Message"></textarea>
|
||||
</div>
|
||||
|
||||
@ -33,6 +23,6 @@ description = "Contact Me Form"
|
||||
</div>
|
||||
|
||||
<button id="simpleContactSubmitButton" type="submit" class="btn btn-outline-primary">Send</button>
|
||||
<button type="reset" class="btn btn-outline-danger">Cancel</button>
|
||||
<button type="reset" class="btn btn-outline-secondary">Cancel</button>
|
||||
|
||||
</form>
|
@ -0,0 +1,5 @@
|
||||
==
|
||||
<div id="feedarea" class="col-container masonry"><img src="{{ 'assets/images/loading.svg' | theme }}"></div>
|
||||
{% put scripts %}
|
||||
<script type="text/javascript">const renderPartial = '{{ renderPartial }}'; var feedItemClass = '{{ feeditemclass }}'</script>
|
||||
{% endput %}
|
45
www/themes/jason-williamsca/partials/feed/template.htm
Normal file
45
www/themes/jason-williamsca/partials/feed/template.htm
Normal file
@ -0,0 +1,45 @@
|
||||
==
|
||||
{% if feeditemclass is empty %}
|
||||
{% set feeditemclass = 'masonry-item' %}
|
||||
{% else %}
|
||||
{% set feeditemclass = 'masonry-item ' ~ feeditemclass %}
|
||||
{% endif %}
|
||||
|
||||
{% if posts is empty %}
|
||||
<p>No feed items have been found.</p>
|
||||
{% endif %}
|
||||
|
||||
{% for post in posts %}
|
||||
<div class="{{ feeditemclass }}">
|
||||
<div class="card">
|
||||
{% if post.extra.img %}
|
||||
<img class="card-img-top" src="{{ post.extra.img }}">
|
||||
{% elseif post.extra.lat %}
|
||||
<!--<div>MAP!</div>
|
||||
<img class="card-img-top" src="https://api.mapbox.com/styles/v1/mapbox/light-v10/static/{{ post.extra.lng }},{{ post.extra.lat }},14,0/725x400@2x?access_token=pk.eyJ1IjoiamFzb24zMmRldiIsImEiOiJjazFsNHd5djcwMXptM2htbW8zM3MyZGxuIn0.-TtNNGFysQPQRfGR1P8DUA">-->
|
||||
<img class="card-img-top" src="https://api.mapbox.com/styles/v1/jason32dev/ckgehopjc2o1y19o09aqs4x11/static/pin-l+B05C71({{ post.extra.lng }},{{ post.extra.lat }})/{{ post.extra.lng }},{{ post.extra.lat }},14,0/545x300@2x?access_token=pk.eyJ1IjoiamFzb24zMmRldiIsImEiOiJjazFsNHd5djcwMXptM2htbW8zM3MyZGxuIn0.-TtNNGFysQPQRfGR1P8DUA">
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
{% if post.channel.slug == 'twitter' or post.channel.slug == 'instagram' %}
|
||||
{% if post.title | striptags | length < 100 %}
|
||||
<h2 class="card-title">{{ post.title | raw | nl2br }}</h2>
|
||||
{% elseif post.title | striptags | length < 150 %}
|
||||
<p class="card-text main">{{ post.title | raw | nl2br }}</p>
|
||||
{% else %}
|
||||
<p class="card-text">{{ post.title | raw | nl2br }}</p>
|
||||
{% endif %}
|
||||
{% elseif post.channel.slug == 'foursquare' %}
|
||||
{% if post.body %}<p class="card-text main">{{ post.body | raw | nl2br }}</p>{% endif %}
|
||||
<h2 class="card-title mb-1">{{ post.title }}</h2>
|
||||
<p class="card-text">{{ post.extra.address }}</p>
|
||||
{% endif %}
|
||||
<div class="feedcard-footer">
|
||||
<p class="float-right feedcard-itemtype">{{ post.friendlytime }} <i class="{{ post.channel.icon }}" style="color: {{ post.channel.colour }};"></i></p>
|
||||
{% if post.channel.slug == 'twitter' and post.extra.opuser %}
|
||||
<p class="float-left feedcard-attribution"><i class="fas fa-retweet"></i> <a href="https://twitter.com/{{ post.extra.opuser }}">{{ post.extra.opname }}</a> <img src="{{ post.extra.opimg }}"></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
32
www/themes/jason-williamsca/partials/pgpkey.htm
Normal file
32
www/themes/jason-williamsca/partials/pgpkey.htm
Normal file
@ -0,0 +1,32 @@
|
||||
==
|
||||
<textarea readonly style="width: 100%; height: 500px; font-family: 'Courier New', Courier, monospace">
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v2
|
||||
|
||||
mQENBFXSMV4BCADJvhoUfskuz9lpiSYy/1uBo0C0j++onaqN0Cwv/fcDvG2NkdV6
|
||||
WMoltJwpUgc4qTwnGroa8Vnf/jXDJnpvvaf4h/zg2O47pwAFsX7eZ9+Djdc5BQhV
|
||||
lD7YX2OtBM5RybnhQAiJgZEi6OFhZnPQAHD6//7E5PhPHRQIs4sezZdrepJw8Bre
|
||||
8qHdM0EsUgiB/9fUmC7yXFsPDmTBpq2iVMf1qdet3Cfc7M17mSZY5RABZVErz12U
|
||||
1TNx+Ecr4cY2MWRNUxpt3ncOK8c9hgcc7WoNjlfL0kc3z/3zW5dNXICYktUAiH47
|
||||
0YymTywq2Aeqf7ZQpu9A5F85qwlDWrUk1V4hABEBAAG0Ikphc29uIFdpbGxpYW1z
|
||||
IDxqQHNvbi13aWxsaWFtcy5jYT6JATkEEwEIACMFAlXSMV4CGwMHCwkIBwMCAQYV
|
||||
CAIJCgsEFgIDAQIeAQIXgAAKCRDXYAQ92dYLDJjUB/472nPkJDBVnZi57afMLbYm
|
||||
D4sBcxoMLtp4RgKV0fNLkzxvReXPsRyODKLbfkpcPLlHp3Jene/oaNyvMmO2TE/i
|
||||
k87cjGzdAuYQMmo2GlxrGodx2eK1qPugBZtGkpHHttAOL3RD3TUzAO3l68SH855h
|
||||
AYcrPh8emBaudvEnT36JjdWkR/k0WOhIQjRGt5SLw3cZ/Icz5tfRl0Ak/KTwroqa
|
||||
W12Ttu6o0vYS3jtTRZ6w/+qH806cET2vou9GXcB4xks6op8wrpfHqyWRHwVuhLc5
|
||||
1q4ieWJftIw0C4EjvTzhdoo3OoXvEYx+Riy1eW0XdklbD9ckVCvaD3M5ptAnxEGR
|
||||
uQENBFXSMV4BCADhgLB/wH1+V+CmVQlbSbWKqc3Z/Nsmm+u09ENX94dKOYiuH9EU
|
||||
Br2Oj7bXeRkUSooQbU7xGznokf63u4XkUA+uN+eDA/qZoIPJ5yi7hULcj37tVFbU
|
||||
4bWgJKqpKHg04x+reT5KgV40xjXBqINqevwwdu4R6QO1RpCG9or9HMoaWUP5ROr4
|
||||
Kp75dNPsXMNnZ7UwDhCp/s0sfGROiBT7SAQzmEBLmlBosT/ah7LXvbxUuI3K08Qe
|
||||
K2dWc+yWPM3ykS4z8Sp0l4CrxfGpnL9DV+DvA3ntzA9cQ5Kt6209FsXjHRexxB3h
|
||||
ZsK58E8qlcH8ZKoGtDL5NctBxASE529lX7K7ABEBAAGJAR8EGAEIAAkFAlXSMV4C
|
||||
GwwACgkQ12AEPdnWCwxEoggAmM+QEtuHVH92ZWwXNda2zNUpbizHLoCBJvrf0lEM
|
||||
DpWKCfrZAo3q0D85Xx9vq2QV/nFkMzv0PsIA8tJiEFtwZBmXxbm+YBWrLxDX+QM4
|
||||
KyLGqxWKbLOKmxpqkRO+PmNY4EjNFeu95TeLeeBsohhGbrfKK2K+Kbw57PV9Ppwy
|
||||
5gZuHRO7xpJMBzc04uVVTmnafU6Ee0scbuwCZTB9VLlDdj5/cjQOcANs/Cu6l58z
|
||||
61sfQcmbk/xXXdgB0EYMdNxNTG4bjdUZOAFqviH4ve5kwff6AzdfCTK/aDPc9+5Z
|
||||
jUajxravR3TUzFdhAT2D92TPpmXG0zIYpsGpNLWvQbtjJA==
|
||||
=Spm3
|
||||
-----END PGP PUBLIC KEY BLOCK-----</textarea>
|
@ -1,12 +1,5 @@
|
||||
description = "Social Media Links"
|
||||
==
|
||||
<?php
|
||||
function onStart()
|
||||
{
|
||||
$this->addJs('assets/javascript/tooltips.js');
|
||||
}
|
||||
?>
|
||||
==
|
||||
<div class="social-icons">
|
||||
{% if not nosocial %}
|
||||
<a href="https://www.linkedin.com/in/jaywll/" data-toggle="tooltip" title="LinkedIn">
|
||||
@ -25,9 +18,17 @@ function onStart()
|
||||
<a href="https://www.facebook.com/jaywll/" data-toggle="tooltip" title="Facebook">
|
||||
<i class="fab fa-facebook-f"></i>
|
||||
</a>
|
||||
<a href="https://foursquare.com/jaywll" data-toggle="tooltip" title="Foursquare">
|
||||
<i class="fab fa-foursquare"></i>
|
||||
</a>
|
||||
{% if not nocontact %}
|
||||
<a href="https://foursquare.com/jaywll" data-toggle="tooltip" title="Foursquare">
|
||||
<i class="fab fa-foursquare"></i>
|
||||
</a>
|
||||
<a href="https://www.goodreads.com/jaywll" data-toggle="tooltip" title="Goodreads">
|
||||
<i class="fab fa-goodreads-g"></i>
|
||||
</a>
|
||||
<a href="https://www.vivino.com/users/jaywll" data-toggle="tooltip" title="Vivino">
|
||||
<i class="fas fa-wine-glass-alt"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not nocode %}
|
||||
|
Loading…
Reference in New Issue
Block a user