Add basic news reader functionality
parent
7d30251cd6
commit
ac1e937dfd
@ -1,4 +1,5 @@
|
||||
vendor
|
||||
settings.php
|
||||
nbproject/private
|
||||
*.sync-conflict*
|
||||
*.sync-conflict*
|
||||
/database.mwb.bak
|
@ -1,3 +1,6 @@
|
||||
[submodule "static/css/material-color"]
|
||||
path = static/css/material-color
|
||||
url = https://source.netsyms.com/Netsyms/Material-Color
|
||||
[submodule "static/Shuffle"]
|
||||
path = static/Shuffle
|
||||
url = https://source.netsyms.com/Mirrors/Vestride_Shuffle.git
|
||||
|
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
{
|
||||
"business": "Business",
|
||||
"entertainment": "Entertainment",
|
||||
"general": "General",
|
||||
"health": "Health",
|
||||
"science": "Science",
|
||||
"sports": "Sports",
|
||||
"technology": "Technology"
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class ApiFetcher {
|
||||
|
||||
/**
|
||||
* Gets a URL. Caches and returns the result, but if the cache has a recent copy
|
||||
* return that instead of making the request.
|
||||
*
|
||||
* Basically, the goal is to stay within the free tier of paid APIs :)
|
||||
*
|
||||
* @global Medoo $database
|
||||
* @param string $url
|
||||
* @param array $params key=>value array of URL parameters
|
||||
* @param array $extraParams key=>value array of URL parameters that will be ignored when checking the cache
|
||||
* @param string $expires When the fetched resource should expire from the cache. A string parsable by strtotime()
|
||||
* @return string The content of the requested URL.
|
||||
*/
|
||||
static function get(string $url, array $params = [], array $extraParams = [], string $expires = "+15 minutes"): string {
|
||||
global $database;
|
||||
|
||||
// Delete stale records
|
||||
$database->delete("requestcache", ["expires[<=]" => date("Y-m-d H:i:s")]);
|
||||
|
||||
// Make sure the params are in the same order every time
|
||||
ksort($params);
|
||||
|
||||
$urlparams = [];
|
||||
foreach ($params as $k => $v) {
|
||||
$urlparams[] = urlencode($k) . "=" . urlencode($v);
|
||||
}
|
||||
|
||||
// Make the URL that will be cached
|
||||
$cacheurl = $url . "?" . implode("&", $urlparams);
|
||||
|
||||
foreach ($extraParams as $k => $v) {
|
||||
$urlparams[] = urlencode($k) . "=" . urlencode($v);
|
||||
}
|
||||
|
||||
if ($database->has("requestcache", ["AND" => ["url" => $cacheurl, "expires[>]" => date("Y-m-d H:i:s")]])) {
|
||||
return $database->get("requestcache", "content", ["AND" => ["url" => $cacheurl, "expires[>]" => date("Y-m-d H:i:s")]]);
|
||||
}
|
||||
|
||||
// Make the actual URL that will be requested
|
||||
$requesturl = $url . "?" . implode("&", $urlparams);
|
||||
|
||||
$content = file_get_contents($requesturl);
|
||||
|
||||
// Only insert into db if it didn't fail horribly
|
||||
if ($content !== FALSE) {
|
||||
$database->insert("requestcache", ["url" => $cacheurl, "expires" => date("Y-m-d H:i:s", strtotime($expires)), "content" => $content]);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the given URL from the cache.
|
||||
*
|
||||
* @global Medoo $database
|
||||
* @param string $url
|
||||
* @param array $params key=>value array of URL parameters
|
||||
*/
|
||||
static function removeFromCache(string $url = "", array $params = []) {
|
||||
global $database;
|
||||
|
||||
// Make sure the params are in the same order every time
|
||||
ksort($params);
|
||||
|
||||
$urlparams = [];
|
||||
foreach ($params as $k => $v) {
|
||||
$urlparams[] = urlencode($k) . "=" . urlencode($v);
|
||||
}
|
||||
|
||||
$cacheurl = $url . "?" . implode("&", $urlparams);
|
||||
|
||||
$database->delete("requestcache", ["url" => $cacheurl]);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Load news items from multiple sources.
|
||||
*/
|
||||
class News {
|
||||
|
||||
static $items = [];
|
||||
|
||||
/**
|
||||
* Load news items from each source.
|
||||
* @param array $sources
|
||||
*/
|
||||
static function load(array $sources) {
|
||||
foreach ($sources as $s) {
|
||||
$class = "NewsSource_$s";
|
||||
$source = new $class();
|
||||
$source->loadItems();
|
||||
News::$items = array_merge(News::$items, $source->getItems());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all news items from all sources loaded.
|
||||
* @return array
|
||||
*/
|
||||
static function getItems() {
|
||||
return News::$items;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class NewsCategory {
|
||||
|
||||
private $category;
|
||||
|
||||
const NONE_ALL = -1;
|
||||
const BUSINESS = 1;
|
||||
const ENTERTAINMENT = 2;
|
||||
const GENERAL = 4;
|
||||
const HEALTH = 8;
|
||||
const SCIENCE = 16;
|
||||
const SPORTS = 32;
|
||||
const TECHNOLOGY = 64;
|
||||
|
||||
const CATEGORIES = [
|
||||
self::BUSINESS => "Business",
|
||||
self::ENTERTAINMENT => "Entertainment",
|
||||
self::GENERAL => "General",
|
||||
self::HEALTH => "Health",
|
||||
self::SCIENCE => "Science",
|
||||
self::SPORTS => "Sports",
|
||||
self::TECHNOLOGY => "Technology"
|
||||
];
|
||||
|
||||
public function __construct(int $category) {
|
||||
$this->category = $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the category as an int corresponding to one of the constants.
|
||||
* @return int
|
||||
*/
|
||||
public function get(): int {
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of the category.
|
||||
* @return string
|
||||
*/
|
||||
public function toString(): string {
|
||||
switch ($this->category) {
|
||||
case self::BUSINESS:
|
||||
return "business";
|
||||
case self::ENTERTAINMENT:
|
||||
return "entertainment";
|
||||
case self::GENERAL:
|
||||
return "general";
|
||||
case self::HEALTH:
|
||||
return "health";
|
||||
case self::SCIENCE:
|
||||
return "science";
|
||||
case self::SPORTS:
|
||||
return "sports";
|
||||
case self::TECHNOLOGY:
|
||||
return "technology";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromString(string $category): NewsCategory {
|
||||
$cat = self::NONE_ALL;
|
||||
switch (strtolower($category)) {
|
||||
case "business":
|
||||
$cat = self::BUSINESS;
|
||||
break;
|
||||
case "entertainment":
|
||||
$cat = self::ENTERTAINMENT;
|
||||
break;
|
||||
case "general":
|
||||
$cat = self::GENERAL;
|
||||
break;
|
||||
case "health":
|
||||
$cat = self::HEALTH;
|
||||
break;
|
||||
case "science":
|
||||
$cat = self::SCIENCE;
|
||||
break;
|
||||
case "sports":
|
||||
$cat = self::SPORTS;
|
||||
break;
|
||||
case "technology":
|
||||
case "tech":
|
||||
$cat = self::TECHNOLOGY;
|
||||
break;
|
||||
}
|
||||
return new NewsCategory($cat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a suitable FontAwesome 5 icon for the category.
|
||||
* @return string CSS classes, such as "fas fa-icon".
|
||||
*/
|
||||
public function getIcon(): string {
|
||||
switch ($this->category) {
|
||||
case self::BUSINESS:
|
||||
return "fas fa-briefcase";
|
||||
case self::ENTERTAINMENT:
|
||||
return "fas fa-tv";
|
||||
case self::GENERAL:
|
||||
return "fas fa-info-circle";
|
||||
case self::HEALTH:
|
||||
return "fas fa-heartbeat";
|
||||
case self::SCIENCE:
|
||||
return "fas fa-atom";
|
||||
case self::SPORTS:
|
||||
return "fas fa-futbol";
|
||||
case self::TECHNOLOGY:
|
||||
return "fas fa-laptop";
|
||||
default:
|
||||
return "fas fa-newspaper";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class NewsItem {
|
||||
|
||||
private $img = "";
|
||||
private $headline = "";
|
||||
private $source = "";
|
||||
private $via = "";
|
||||
private $url = "";
|
||||
private $timestamp = 0;
|
||||
private $category;
|
||||
|
||||
function __construct(string $headline = "", string $img = "", string $url = "", string $source = "", string $via = "", int $timestamp = 0, $category = null) {
|
||||
$this->headline = $headline;
|
||||
$this->img = $img;
|
||||
$this->url = $url;
|
||||
$this->source = $source;
|
||||
$this->via = $via;
|
||||
$this->timestamp = $timestamp;
|
||||
if (is_null($category)) {
|
||||
$this->category = new NewsCategory(NewsCategory::GENERAL);
|
||||
} else {
|
||||
$this->category = $category;
|
||||
}
|
||||
}
|
||||
|
||||
function getImage(): string {
|
||||
return $this->img;
|
||||
}
|
||||
|
||||
function getURL(): string {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
function getHeadline(): string {
|
||||
return $this->headline;
|
||||
}
|
||||
|
||||
function getSource(): string {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
function getVia(): string {
|
||||
return $this->via;
|
||||
}
|
||||
|
||||
function getTimestamp(): int {
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
function getCategory(): \NewsCategory {
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for news sources.
|
||||
*/
|
||||
class NewsSource {
|
||||
|
||||
private $items = [];
|
||||
|
||||
function getItems(): array {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch news items from this source.
|
||||
*/
|
||||
function loadItems() {
|
||||
// Do nothing, because this is a dummy source
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class NewsSource_NewsAPI extends NewsSource {
|
||||
|
||||
private $items = [];
|
||||
|
||||
function getItems(): array {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
function loadItems() {
|
||||
$this->loadHeadlines((new NewsCategory(NewsCategory::BUSINESS)));
|
||||
$this->loadHeadlines((new NewsCategory(NewsCategory::ENTERTAINMENT)));
|
||||
$this->loadHeadlines((new NewsCategory(NewsCategory::GENERAL)));
|
||||
$this->loadHeadlines((new NewsCategory(NewsCategory::HEALTH)));
|
||||
$this->loadHeadlines((new NewsCategory(NewsCategory::SCIENCE)));
|
||||
$this->loadHeadlines((new NewsCategory(NewsCategory::SPORTS)));
|
||||
$this->loadHeadlines((new NewsCategory(NewsCategory::TECHNOLOGY)));
|
||||
}
|
||||
|
||||
private function loadHeadlines($category = null, string $country = "us", $apikey = null) {
|
||||
global $SETTINGS;
|
||||
if (is_null($category)) {
|
||||
$category = new NewsCategory(NewsCategory::GENERAL);
|
||||
}
|
||||
if (is_null($apikey)) {
|
||||
$apikey = $SETTINGS['apikeys']['newsapi.org'];
|
||||
}
|
||||
$url = "https://newsapi.org/v2/top-headlines";
|
||||
$params = [];
|
||||
if (!empty($country)) {
|
||||
$params['country'] = $country;
|
||||
}
|
||||
if (!empty($category->toString())) {
|
||||
$params['category'] = $category->toString();
|
||||
}
|
||||
$items = [];
|
||||
$json = ApiFetcher::get($url, $params, ["apiKey" => $apikey]);
|
||||
$data = json_decode($json, TRUE);
|
||||
if ($data['status'] != "ok") {
|
||||
return [];
|
||||
}
|
||||
$articles = $data['articles'];
|
||||
foreach ($articles as $n) {
|
||||
$title = $n['title'];
|
||||
$image = $n['urlToImage'];
|
||||
if (is_null($image)) {
|
||||
continue;
|
||||
}
|
||||
$url = $n['url'];
|
||||
$source = $n['source']['name'];
|
||||
$timestamp = strtotime($n['publishedAt']);
|
||||
|
||||
$items[] = new NewsItem($title, $image, $url, $source, "NewsAPI.org", $timestamp, $category);
|
||||
}
|
||||
|
||||
$this->items = array_merge($this->items, $items);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class NewsSource_Reddit extends NewsSource {
|
||||
|
||||
private $items = [];
|
||||
|
||||
function getItems(): array {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
function loadItems() {
|
||||
$this->loadSubreddit("news+worldnews+UpliftingNews", new NewsCategory(NewsCategory::GENERAL));
|
||||
//$this->loadSubreddit("technology", new NewsCategory(NewsCategory::TECHNOLOGY));
|
||||
}
|
||||
|
||||
private function loadSubreddit(string $subreddit, NewsCategory $category) {
|
||||
$items = [];
|
||||
$json = ApiFetcher::get("https://www.reddit.com/r/$subreddit.json", ["limit" => "50"]);
|
||||
$news = json_decode($json, TRUE)['data']['children'];
|
||||
foreach ($news as $d) {
|
||||
$n = $d['data'];
|
||||
|
||||
// Ignore non-linky or NSFW posts
|
||||
if ($n['is_self']) {
|
||||
continue;
|
||||
}
|
||||
if ($n['is_video']) {
|
||||
continue;
|
||||
}
|
||||
if ($n['over_18']) {
|
||||
continue;
|
||||
}
|
||||
if (empty($n['url'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Thumbnail image
|
||||
$image = "";
|
||||
if (isset($n['thumbnail']) && $n['thumbnail'] != "default") {
|
||||
$image = $n['thumbnail'];
|
||||
}
|
||||
if (isset($n['preview']['images'][0]['resolutions'])) {
|
||||
if (isset($n['preview']['images'][0]['resolutions'][2]['url'])) {
|
||||
$image = $n['preview']['images'][0]['resolutions'][2]['url'];
|
||||
} else if (isset($n['preview']['images'][0]['resolutions'][1]['url'])) {
|
||||
$image = $n['preview']['images'][0]['resolutions'][1]['url'];
|
||||
}
|
||||
}
|
||||
|
||||
$source = "reddit.com";
|
||||
if (!empty($n['domain'])) {
|
||||
$source = $n['domain'];
|
||||
}
|
||||
|
||||
$timestamp = time();
|
||||
if (!empty($n['created'])) {
|
||||
$timestamp = $n['created'];
|
||||
}
|
||||
|
||||
$items[] = new NewsItem($n['title'], $image, $n['url'], $source, "reddit", $timestamp, $category);
|
||||
}
|
||||
|
||||
$this->items = array_merge($this->items, $items);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Thumbnail {
|
||||
|
||||
/**
|
||||
* Fetches an image URL, resizes it, and returns JPEG thumbnail data.
|
||||
* Based on https://stackoverflow.com/a/29024968
|
||||
* @param string $url
|
||||
* @param int $width
|
||||
* @param int,bool $height
|
||||
* @return type
|
||||
*/
|
||||
static function getThumbnailFromUrl(string $url, int $width = 150, $height = true) {
|
||||
|
||||
// download and create gd image
|
||||
$image = imagecreatefromstring(file_get_contents($url));
|
||||
|
||||
// calculate resized ratio
|
||||
// Note: if $height is set to TRUE then we automatically calculate the height based on the ratio
|
||||
$height = $height === true ? (imagesy($image) * $width / imagesx($image)) : $height;
|
||||
|
||||
// create image
|
||||
$output = imagecreatetruecolor($width, $height);
|
||||
imagecopyresampled($output, $image, 0, 0, 0, 0, $width, $height, ImageSX($image), ImageSY($image));
|
||||
|
||||
ob_end_flush();
|
||||
ob_start();
|
||||
imagejpeg($output, null, 75);
|
||||
$imagedata = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// return resized image
|
||||
return $imagedata; // if you need to use it
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes some data to base64.
|
||||
* @param type $img
|
||||
* @return string
|
||||
*/
|
||||
static function imgToBase64($img): string {
|
||||
return base64_encode($img);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base64 data: URI for a jpeg image
|
||||
* @param type $img
|
||||
* @return string
|
||||
*/
|
||||
static function jpegToBase64URI($img): string {
|
||||
return "data:image/jpeg;base64," . self::imgToBase64($img);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Weather {
|
||||
|
||||
}
|
@ -1,6 +1,54 @@
|
||||
<?php
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
News::load($SETTINGS["sources"]["news"]);
|
||||
|
||||
$newsitems = News::getItems();
|
||||
|
||||
// Sort by category
|
||||
$itemsbycategory = [];
|
||||
foreach ($newsitems as $item) {
|
||||
$itemsbycategory[$item->getCategory()->toString()][] = $item;
|
||||
}
|
||||
?>
|
||||
<h1>Hello World</h1>
|
||||
|
||||
<?php
|
||||
foreach ($itemsbycategory as $category => $items) {
|
||||
$cat = NewsCategory::fromString($category);
|
||||
?>
|
||||
<p class="lead">
|
||||
<i class="<?php echo $cat->getIcon(); ?> fa-fw"></i> <?php $Strings->get($cat->toString()); ?>
|
||||
</p>
|
||||
<div class="list-group mb-4">
|
||||
<?php
|
||||
shuffle($items);
|
||||
$count = 0;
|
||||
foreach ($items as $item) {
|
||||
if ($count >= 5) {
|
||||
break;
|
||||
}
|
||||
$count++;
|
||||
?>
|
||||
<div class="list-group-item d-flex justify-content-between">
|
||||
<a class="text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
|
||||
<h4>
|
||||
<?php echo htmlentities($item->getHeadline()); ?>
|
||||
</h4>
|
||||
<p class="text-muted"><?php echo htmlentities($item->getSource()); ?></p>
|
||||
</a>
|
||||
|
||||
<?php if (!empty($item->getImage())) { ?>
|
||||
<img src="<?php echo Thumbnail::jpegToBase64URI(Thumbnail::getThumbnailFromUrl($item->getImage(), 100)); ?>" alt="" class="img-fluid">
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
News::load($SETTINGS["sources"]["news"]);
|
||||
|
||||
$newsitems = News::getItems();
|
||||
?>
|
||||
|
||||
<div class="btn-toolbar mb-4" role="toolbar">
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<?php foreach (NewsCategory::CATEGORIES as $cat) { ?>
|
||||
<label class="btn btn-secondary">
|
||||
<input type="radio" name="newscategory" id="category-btn-<?php echo strtolower($cat); ?>" value="<?php echo strtolower($cat); ?>" autocomplete="off"> <?php $Strings->get($cat); ?>
|
||||
</label>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="news-grid">
|
||||
|
||||
<?php foreach ($newsitems as $item) { ?>
|
||||
<div class="col-12 col-sm-6 col-md-6 col-lg-4 px-1 m-0 grid__brick" data-groups='["<?php echo $item->getCategory()->toString(); ?>"]'>
|
||||
<div class="card mb-2">
|
||||
<?php if (!empty($item->getImage())) { ?>
|
||||
<img src="<?php echo $item->getImage(); ?>" class="card-img-top" alt="">
|
||||
<?php } ?>
|
||||
<a class="card-body text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
|
||||
<h4 class="card-title">
|
||||
<?php echo htmlentities($item->getHeadline()); ?>
|
||||
</h4>
|
||||
<p class="small"><?php echo htmlentities($item->getSource()); ?></p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="col-1 sizer-element"></div>
|
||||
|
||||
</div>
|
@ -0,0 +1 @@
|
||||
Subproject commit c742bfaefc8908170825227a7385f20e0ba5da37
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
window.shuffleInstance = new window.Shuffle(document.getElementById('news-grid'), {
|
||||
itemSelector: '.grid__brick',
|
||||
sizer: '.sizer-element'
|
||||
});
|
||||
|
||||
$("input[name=newscategory]").on("change", function () {
|
||||
window.shuffleInstance.filter($(this).val());
|
||||
$(this).button('toggle');
|
||||
});
|
||||
|
||||
setInterval(function () {
|
||||
window.shuffleInstance.layout();
|
||||
}, 500);
|
Loading…
Reference in New Issue