Browse Source

Add basic news reader functionality

master
Skylar Ittner 3 years ago
parent
commit
ac1e937dfd
  1. 3
      .gitignore
  2. 3
      .gitmodules
  3. 4
      action.php
  4. 4
      composer.json
  5. 77
      composer.lock
  6. BIN
      database.mwb
  7. 9
      langs/en/categories.json
  8. 85
      lib/ApiFetcher.lib.php
  9. 37
      lib/News.lib.php
  10. 123
      lib/NewsCategory.lib.php
  11. 61
      lib/NewsItem.lib.php
  12. 27
      lib/NewsSource.lib.php
  13. 66
      lib/NewsSource_NewsAPI.lib.php
  14. 72
      lib/NewsSource_Reddit.lib.php
  15. 60
      lib/Thumbnail.lib.php
  16. 11
      lib/Weather.lib.php
  17. 2
      nbproject/project.xml
  18. 23
      pages.php
  19. 54
      pages/home.php
  20. 41
      pages/news.php
  21. 23
      settings.template.php
  22. 1
      static/Shuffle
  23. 19
      static/js/news.js

3
.gitignore

@ -1,4 +1,5 @@
vendor
settings.php
nbproject/private
*.sync-conflict*
*.sync-conflict*
/database.mwb.bak

3
.gitmodules

@ -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

4
action.php

@ -35,4 +35,8 @@ switch ($VARS['action']) {
session_destroy();
header('Location: index.php?logout=1');
die("Logged out.");
case "thumbnail":
header("Content-Type: image/jpeg");
// TODO
break;
}

4
composer.json

@ -1,6 +1,6 @@
{
"name": "netsyms/business-app-template",
"description": "Template for a webapp integrated with an AccountHub server.",
"name": "netsyms/todaystream",
"description": "News and weather app.",
"type": "project",
"require": {
"catfan/medoo": "^1.5",

77
composer.lock

@ -4,21 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "5c7439c6e041764f2f6b0270a95ab3ae",
"content-hash": "e4e700119f47d2f68b0ed82abaf8c5c6",
"content-hash": "5642e5d15688c9510f4b0d1b4ceed67e",
"packages": [
{
"name": "catfan/medoo",
"version": "v1.5.7",
"version": "v1.6.1",
"source": {
"type": "git",
"url": "https://github.com/catfan/Medoo.git",
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf"
"reference": "53a02b300d673f716cb06bf0e24fd774ec53939f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf",
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf",
"url": "https://api.github.com/repos/catfan/Medoo/zipball/53a02b300d673f716cb06bf0e24fd774ec53939f",
"reference": "53a02b300d673f716cb06bf0e24fd774ec53939f",
"shasum": ""
},
"require": {
@ -64,7 +63,7 @@
"sql",
"sqlite"
],
"time": "2018-06-14 18:59:08"
"time": "2018-12-08T20:24:23+00:00"
},
{
"name": "guzzlehttp/guzzle",
@ -129,7 +128,7 @@
"rest",
"web service"
],
"time": "2018-04-22 15:46:56"
"time": "2018-04-22T15:46:56+00:00"
},
{
"name": "guzzlehttp/promises",
@ -180,36 +179,37 @@
"keywords": [
"promise"
],
"time": "2016-12-20 10:07:11"
"time": "2016-12-20T10:07:11+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.4.2",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
"reference": "9f83dded91781a01c63574e387eaa769be769115"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
"reference": "9f83dded91781a01c63574e387eaa769be769115",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@ -239,13 +239,14 @@
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2017-03-20 17:10:46"
"time": "2018-12-04T20:46:45+00:00"
},
{
"name": "psr/http-message",
@ -295,7 +296,47 @@
"request",
"response"
],
"time": "2016-08-06 14:39:51"
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "2.0.5",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "~3.7.0",
"satooshi/php-coveralls": ">=1.0"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"time": "2016-02-11T07:05:27+00:00"
}
],
"packages-dev": [],

BIN
database.mwb

9
langs/en/categories.json

@ -0,0 +1,9 @@
{
"business": "Business",
"entertainment": "Entertainment",
"general": "General",
"health": "Health",
"science": "Science",
"sports": "Sports",
"technology": "Technology"
}

85
lib/ApiFetcher.lib.php

@ -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]);
}
}

37
lib/News.lib.php

@ -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;
}
}

123
lib/NewsCategory.lib.php

@ -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";
}
}
}

61
lib/NewsItem.lib.php

@ -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;
}
}

27
lib/NewsSource.lib.php

@ -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
}
}

66
lib/NewsSource_NewsAPI.lib.php

@ -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);
}
}

72
lib/NewsSource_Reddit.lib.php

@ -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);
}
}

60
lib/Thumbnail.lib.php

@ -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);
}
}

11
lib/Weather.lib.php

@ -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 {
}

2
nbproject/project.xml

@ -3,7 +3,7 @@
<type>org.netbeans.modules.php.project</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/php-project/1">
<name>BusinessAppTemplate</name>
<name>TodayStream</name>
</data>
</configuration>
</project>

23
pages.php

@ -7,19 +7,24 @@
// List of pages and metadata
define("PAGES", [
"home" => [
"title" => "Home",
"title" => "Overview",
"navbar" => true,
"icon" => "fas fa-home"
],
"404" => [
"title" => "404 error"
"icon" => "fas fa-home",
"scripts" => [
"static/Shuffle/dist/shuffle.min.js",
"static/js/home.js"
]
],
"form" => [
"title" => "Form",
"news" => [
"title" => "News",
"navbar" => true,
"icon" => "fas fa-file-alt",
"icon" => "fas fa-newspaper",
"scripts" => [
"static/js/form.js"
"static/Shuffle/dist/shuffle.min.js",
"static/js/news.js"
]
],
"404" => [
"title" => "404 error"
]
]);

54
pages/home.php

@ -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
}
?>

41
pages/news.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>

23
settings.template.php

@ -19,14 +19,14 @@ $SETTINGS = [
// See http://medoo.in/api/new for info
"database" => [
"type" => "mysql",
"name" => "app",
"name" => "todaystream",
"server" => "localhost",
"user" => "app",
"user" => "",
"password" => "",
"charset" => "utf8"
],
// Name of the app.
"site_title" => "Web App Template",
"site_title" => "TodayStream",
// Settings for connecting to the AccountHub server.
"accounthub" => [
// URL for the API endpoint
@ -36,6 +36,21 @@ $SETTINGS = [
// API key
"key" => "123"
],
// API keys for data sources
"apikeys" => [
"newsapi.org" => "",
"darksky.net" => "",
],
// Which data sources to use
"sources" => [
"news" => [
"NewsAPI",
"Reddit"
],
"weather" => [
"DarkSky"
]
],
// List of required user permissions to access this app.
"permissions" => [
],
@ -44,7 +59,7 @@ $SETTINGS = [
// Language to use for localization. See langs folder to add a language.
"language" => "en",
// Shown in the footer of all the pages.
"footer_text" => "",
"footer_text" => "Data sources: <a href=\"https://newsapi.org\">NewsAPI.org</a>, <a href=\"https://reddit.com\">Reddit</a>, <a href=\"https://darksky.net\">Dark Sky</a>",
// Also shown in the footer, but with "Copyright <current_year>" in front.
"copyright" => "Netsyms Technologies",
// Base URL for building links relative to the location of the app.

1
static/Shuffle

@ -0,0 +1 @@
Subproject commit c742bfaefc8908170825227a7385f20e0ba5da37

19
static/js/news.js

@ -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…
Cancel
Save