Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
34 changed files with 3042 additions and 307 deletions
Showing only changes of commit 54371bf76a - Show all commits

View File

@ -54,6 +54,7 @@ class BaseController {
public function __construct(Config &$config, Array $web) public function __construct(Config &$config, Array $web)
{ {
$this->config = $config; $this->config = $config;
$this->base_data['config'] = $config;
list($request, $response) = $web; list($request, $response) = $web;
$this->request = $request; $this->request = $request;
@ -70,6 +71,24 @@ class BaseController {
$this->output(); $this->output();
} }
/**
* Get a class member
*
* @param string $key
* @return object
*/
public function __get($key)
{
$allowed = ['request', 'response', 'config'];
if (in_array($key, $allowed))
{
return $this->$key;
}
return NULL;
}
/** /**
* Get the string output of a partial template * Get the string output of a partial template
* *
@ -95,7 +114,7 @@ class BaseController {
if ( ! is_file($template_path)) if ( ! is_file($template_path))
{ {
throw new Exception("Invalid template : {$path}"); throw new InvalidArgumentException("Invalid template : {$path}");
} }
ob_start(); ob_start();
@ -152,16 +171,9 @@ class BaseController {
*/ */
public function redirect($url, $code, $type="anime") public function redirect($url, $code, $type="anime")
{ {
$url = full_url($url, $type); $url = $this->config->full_url($url, $type);
$codes = [ $this->response->redirect->to($url, $code);
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other'
];
header("HTTP/1.1 {$code} {$codes[$code]}");
header("Location: {$url}");
} }
/** /**
@ -189,7 +201,7 @@ class BaseController {
public function logout() public function logout()
{ {
session_destroy(); session_destroy();
$this->response->redirect->seeOther(full_url('')); $this->response->redirect->seeOther($this->config->full_url(''));
} }
/** /**
@ -228,7 +240,7 @@ class BaseController {
) )
) )
{ {
$this->response->redirect->afterPost(full_url('', $this->base_data['url_type'])); $this->response->redirect->afterPost($this->config->full_url('', $this->base_data['url_type']));
return; return;
} }

View File

@ -73,7 +73,7 @@ class BaseModel {
} }
else else
{ {
throw new Exception("Couldn't cache images because they couldn't be downloaded."); throw new DomainException("Couldn't cache images because they couldn't be downloaded.");
} }
// Resize the image // Resize the image

View File

@ -52,5 +52,77 @@ class Config {
return NULL; return NULL;
} }
/**
* Get the base url for css/js/images
*
* @return string
*/
function asset_url(/*...*/)
{
$args = func_get_args();
$base_url = rtrim($this->__get('asset_path'), '/');
array_unshift($args, $base_url);
return implode("/", $args);
}
/**
* Get the base url from the config
*
* @param string $type - (optional) The controller
* @return string
*/
function base_url($type="anime")
{
$config_path = trim($this->__get("{$type}_path"), "/");
$config_host = $this->__get("{$type}_host");
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
$path = ($config_path !== '') ? $config_path : "";
return implode("/", ['/', $host, $path]);
}
/**
* Generate full url path from the route path based on config
*
* @param string $path - (optional) The route path
* @param string $type - (optional) The controller (anime or manga), defaults to anime
* @return string
*/
function full_url($path="", $type="anime")
{
$config_path = trim($this->__get("{$type}_path"), "/");
$config_host = $this->__get("{$type}_host");
$config_default_route = $this->__get("default_{$type}_path");
// Remove beginning/trailing slashes
$config_path = trim($config_path, '/');
$path = trim($path, '/');
// Remove any optional parameters from the route
$path = preg_replace('`{/.*?}`i', '', $path);
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
// Set the default view
if ($path === '')
{
$path .= trim($config_default_route, '/');
if ($this->__get('default_to_list_view')) $path .= '/list';
}
// Set an leading folder
if ($config_path !== '')
{
$path = "{$config_path}/{$path}";
}
return "//{$host}/{$path}";
}
} }
// End of config.php // End of config.php

View File

@ -64,7 +64,7 @@ class Router {
{ {
global $defaultHandler; global $defaultHandler;
$raw_route = $this->request->server->get('REQUEST_URI'); $raw_route = parse_url($this->request->server->get('REQUEST_URI'), \PHP_URL_PATH);
$route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route); $route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route);
$route_path = "/" . trim($route_path, '/'); $route_path = "/" . trim($route_path, '/');
@ -108,14 +108,14 @@ class Router {
$failure = $this->router->getFailedRoute(); $failure = $this->router->getFailedRoute();
$defaultHandler->addDataTable('failed_route', (array)$failure); $defaultHandler->addDataTable('failed_route', (array)$failure);
$controller_name = '\\AnimeClient\\BaseController'; /*$controller_name = '\\AnimeClient\\BaseController';
$action_method = 'outputHTML'; $action_method = 'outputHTML';
$params = [ $params = [
'template' => '404', 'template' => '404',
'data' => [ 'data' => [
'title' => 'Page Not Found' 'title' => 'Page Not Found'
] ]
]; ];*/
} }
else else
{ {
@ -148,7 +148,7 @@ class Router {
*/ */
public function get_route_type() public function get_route_type()
{ {
$route_type = ""; $route_type = $this->config->default_list;
$host = $this->request->server->get("HTTP_HOST"); $host = $this->request->server->get("HTTP_HOST");
$request_uri = $this->request->server->get('REQUEST_URI'); $request_uri = $this->request->server->get('REQUEST_URI');

View File

@ -38,87 +38,6 @@ function is_not_selected($a, $b)
return ($a !== $b) ? 'selected' : ''; return ($a !== $b) ? 'selected' : '';
} }
/**
* Get the base url for css/js/images
*
* @return string
*/
function asset_url(/*...*/)
{
global $config;
$args = func_get_args();
$base_url = rtrim($config->asset_path, '/');
array_unshift($args, $base_url);
return implode("/", $args);
}
/**
* Get the base url from the config
*
* @param string $type - (optional) The controller
# @param object $config - (optional) Config
* @return string
*/
function base_url($type="anime", $config=NULL)
{
if (is_null($config)) global $config;
$config_path = trim($config->{"{$type}_path"}, "/");
$config_host = $config->{"{$type}_host"};
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
$path = ($config_path !== '') ? $config_path : "";
return implode("/", ['/', $host, $path]);
}
/**
* Generate full url path from the route path based on config
*
* @param string $path - (optional) The route path
* @param string $type - (optional) The controller (anime or manga), defaults to anime
# @param object $config - (optional) Config
* @return string
*/
function full_url($path="", $type="anime", $config=NULL)
{
if (is_null($config)) global $config;
$config_path = trim($config->{"{$type}_path"}, "/");
$config_host = $config->{"{$type}_host"};
$config_default_route = $config->{"default_{$type}_path"};
// Remove beginning/trailing slashes
$config_path = trim($config_path, '/');
$path = trim($path, '/');
// Remove any optional parameters from the route
$path = preg_replace('`{/.*?}`i', '', $path);
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
// Set the default view
if ($path === '')
{
$path .= trim($config_default_route, '/');
if ($config->default_to_list_view) $path .= '/list';
}
// Set an leading folder
if ($config_path !== '')
{
$path = "{$config_path}/{$path}";
}
return "//{$host}/{$path}";
}
/** /**
* Get the last segment of the current url * Get the last segment of the current url
* *
@ -131,4 +50,19 @@ function last_segment()
return end($segments); return end($segments);
} }
/**
* Determine whether to show the sub-menu
*
* @return bool
*/
function is_view_page()
{
$blacklist = ['edit', 'add', 'update', 'login', 'logout'];
$page_segments = explode("/", $_SERVER['REQUEST_URI']);
$intersect = array_intersect($page_segments, $blacklist);
return empty($intersect);
}
// End of functions.php // End of functions.php

View File

@ -32,6 +32,9 @@ $config = [
'anime_path' => '', 'anime_path' => '',
'manga_path' => '', 'manga_path' => '',
// Which list should be the default?
'default_list' => 'anime', // anime or manga
// Default pages for anime/manga // Default pages for anime/manga
'default_anime_path' => '/watching', 'default_anime_path' => '/watching',
'default_manga_path' => '/all', 'default_manga_path' => '/all',

View File

@ -34,6 +34,12 @@ return [
'show_message.js', 'show_message.js',
'anime_edit.js', 'anime_edit.js',
'manga_edit.js' 'manga_edit.js'
],
'collection' => [
'lib/jquery.min.js',
'lib/jquery.throttle-debounce.js',
'lib/jsrender.js',
'collection.js'
] ]
]; ];

View File

@ -33,6 +33,10 @@ return [
'code' => '301' 'code' => '301'
] ]
], ],
'search' => [
'path' => '/search',
'action' => ['search'],
],
'all' => [ 'all' => [
'path' => '/all{/view}', 'path' => '/all{/view}',
'action' => ['anime_list'], 'action' => ['anime_list'],
@ -99,14 +103,36 @@ return [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
] ]
], ],
'collection_add_form' => [
'path' => '/collection/add',
'action' => ['collection_form'],
'params' => [],
],
'collection_edit_form' => [
'path' => '/collection/edit/{id}',
'action' => ['collection_form'],
'tokens' => [
'id' => '[0-9]+'
]
],
'collection_add' => [
'path' => '/collection/add',
'action' => ['collection_add'],
'verb' => 'post'
],
'collection_edit' => [
'path' => '/collection/edit',
'action' => ['collection_edit'],
'verb' => 'post'
],
'collection' => [ 'collection' => [
'path' => '/collection{/view}', 'path' => '/collection/view{/view}',
'action' => ['collection'], 'action' => ['collection'],
'params' => [], 'params' => [],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
] ]
] ],
], ],
'manga' => [ 'manga' => [
'index' => [ 'index' => [

View File

@ -38,7 +38,7 @@ class AnimeController extends BaseController {
'On Hold' => '/on_hold{/view}', 'On Hold' => '/on_hold{/view}',
'Dropped' => '/dropped{/view}', 'Dropped' => '/dropped{/view}',
'Completed' => '/completed{/view}', 'Completed' => '/completed{/view}',
'Collection' => '/collection{/view}', 'Collection' => '/collection/view{/view}',
'All' => '/all{/view}' 'All' => '/all{/view}'
]; ];
@ -61,9 +61,21 @@ class AnimeController extends BaseController {
'url_type' => 'anime', 'url_type' => 'anime',
'other_type' => 'manga', 'other_type' => 'manga',
'nav_routes' => $this->nav_routes, 'nav_routes' => $this->nav_routes,
'config' => $this->config,
]; ];
} }
/**
* Search for anime
*
* @return void
*/
public function search()
{
$query = $this->request->query->get('query');
$this->outputJSON($this->model->search($query));
}
/** /**
* Show a portion, or all of the anime list * Show a portion, or all of the anime list
* *
@ -104,10 +116,66 @@ class AnimeController extends BaseController {
$this->outputHTML('anime/' . $view_map[$view], [ $this->outputHTML('anime/' . $view_map[$view], [
'title' => WHOSE . " Anime Collection", 'title' => WHOSE . " Anime Collection",
'sections' => $data 'sections' => $data,
'genres' => $this->collection_model->get_genre_list()
]); ]);
} }
/**
* Show the anime collection add/edit form
*
* @param int $id
* @return void
*/
public function collection_form($id=NULL)
{
$action = (is_null($id)) ? "Add" : "Edit";
$this->outputHTML('anime/collection_' . strtolower($action), [
'action' => $action,
'action_url' => $this->config->full_url("collection/" . strtolower($action)),
'title' => WHOSE . " Anime Collection · {$action}",
'media_items' => $this->collection_model->get_media_type_list(),
'item' => ($action === "Edit") ? $this->collection_model->get($id) : []
]);
}
/**
* Update a collection item
*
* @return void
*/
public function collection_edit()
{
$data = $this->request->post->get();
if ( ! array_key_exists('hummingbird_id', $data))
{
$this->redirect("collection/view", 303, "anime");
}
$this->collection_model->update($data);
$this->redirect("collection/view", 303, "anime");
}
/**
* Add a collection item
*
* @return void
*/
public function collection_add()
{
$data = $this->request->post->get();
if ( ! array_key_exists('id', $data))
{
$this->redirect("collection/view", 303, "anime");
}
$this->collection_model->add($data);
$this->redirect("collection/view", 303, "anime");
}
/** /**
* Update an anime item * Update an anime item
* *
@ -115,7 +183,7 @@ class AnimeController extends BaseController {
*/ */
public function update() public function update()
{ {
print_r($this->model->update($this->request->post->get())); $this->outputJSON($this->model->update($this->request->post->get()));
} }
} }
// End of AnimeController.php // End of AnimeController.php

View File

@ -43,6 +43,7 @@ class MangaController extends BaseController {
parent::__construct($config, $web); parent::__construct($config, $web);
$this->model = new MangaModel($config); $this->model = new MangaModel($config);
$this->base_data = [ $this->base_data = [
'config' => $this->config,
'url_type' => 'manga', 'url_type' => 'manga',
'other_type' => 'anime', 'other_type' => 'anime',
'nav_routes' => $this->nav_routes 'nav_routes' => $this->nav_routes

View File

@ -42,6 +42,49 @@ class AnimeCollectionModel extends BaseDBModel {
$this->json_import(); $this->json_import();
} }
/**
* Get genres for anime collection items
*
* @param array $filter
* @return array
*/
public function get_genre_list($filter=[])
{
$this->db->select('hummingbird_id, genre')
->from('genre_anime_set_link gl')
->join('genres g', 'g.id=gl.genre_id', 'left');
if ( ! empty($filter)) $this->db->where_in('hummingbird_id', $filter);
$query = $this->db->order_by('hummingbird_id')
->order_by('genre')
->get();
$output = [];
foreach($query->fetchAll(\PDO::FETCH_ASSOC) as $row)
{
$id = $row['hummingbird_id'];
$genre = $row['genre'];
// Empty genre names aren't useful
if (empty($genre)) continue;
if (array_key_exists($id, $output))
{
array_push($output[$id], $genre);
}
else
{
$output[$id] = [$genre];
}
}
return $output;
}
/** /**
* Get collection from the database, and organize by media type * Get collection from the database, and organize by media type
* *
@ -68,6 +111,42 @@ class AnimeCollectionModel extends BaseDBModel {
return $collection; return $collection;
} }
/**
* Get list of media types
*
* @return array
*/
public function get_media_type_list()
{
$output = array();
$query = $this->db->select('id, type')
->from('media')
->get();
foreach($query->fetchAll(\PDO::FETCH_ASSOC) as $row)
{
$output[$row['id']] = $row['type'];
}
return $output;
}
/**
* Get item from collection for editing
*
* @param int $id
* @return array
*/
public function get_collection_entry($id)
{
$query = $this->db->from('anime_set')
->where('hummingbird_id', (int) $id)
->get();
return $query->fetch(\PDO::FETCH_ASSOC);
}
/** /**
* Get full collection from the database * Get full collection from the database
* *
@ -87,6 +166,67 @@ class AnimeCollectionModel extends BaseDBModel {
return $query->fetchAll(\PDO::FETCH_ASSOC); return $query->fetchAll(\PDO::FETCH_ASSOC);
} }
/**
* Add an item to the anime collection
*
* @param array $data
* @return void
*/
public function add($data)
{
$anime = (object) $this->anime_model->get_anime($data['id']);
$this->db->set([
'hummingbird_id' => $data['id'],
'slug' => $anime->slug,
'title' => $anime->title,
'alternate_title' => $anime->alternate_title,
'show_type' => $anime->show_type,
'age_rating' => $anime->age_rating,
'cover_image' => basename($this->get_cached_image($anime->cover_image, $anime->slug, 'anime')),
'episode_count' => $anime->episode_count,
'episode_length' => $anime->episode_length,
'media_id' => $data['media_id'],
'notes' => $data['notes']
])->insert('anime_set');
$this->update_genre($data['id']);
}
/**
* Update a collection item
*
* @param array $data
* @return void
*/
public function update($data)
{
// If there's no id to update, don't update
if ( ! array_key_exists('hummingbird_id', $data)) return;
$id = $data['hummingbird_id'];
unset($data['hummingbird_id']);
$this->db->set($data)
->where('hummingbird_id', $id)
->update('anime_set');
}
/**
* Get the details of a collection item
*
* @param int $hummingbird_id
* @return array
*/
public function get($hummingbird_id)
{
$query = $this->db->from('anime_set')
->where('hummingbird_id', $hummingbird_id)
->get();
return $query->fetch(\PDO::FETCH_ASSOC);
}
/** /**
* Import anime into collection from a json file * Import anime into collection from a json file
* *
@ -108,7 +248,7 @@ class AnimeCollectionModel extends BaseDBModel {
'alternate_title' => $item->alternate_title, 'alternate_title' => $item->alternate_title,
'show_type' => $item->show_type, 'show_type' => $item->show_type,
'age_rating' => $item->age_rating, 'age_rating' => $item->age_rating,
'cover_image' => $this->get_cached_image($item->cover_image, $item->slug, 'anime'), 'cover_image' => basename($this->get_cached_image($item->cover_image, $item->slug, 'anime')),
'episode_count' => $item->episode_count, 'episode_count' => $item->episode_count,
'episode_length' => $item->episode_length 'episode_length' => $item->episode_length
])->insert('anime_set'); ])->insert('anime_set');
@ -122,22 +262,67 @@ class AnimeCollectionModel extends BaseDBModel {
} }
/** /**
* Update genre information * Update genre information for selected anime
* *
* @return void * @return void
*/ */
private function update_genres() private function update_genre($anime_id)
{
$genre_info = $this->get_genre_data();
extract($genre_info);
// Get api information
$anime = $this->anime_model->get_anime($anime_id);
foreach($anime['genres'] as $genre)
{
// Add genres that don't currently exist
if ( ! in_array($genre['name'], $genres))
{
$this->db->set('genre', $genre['name'])
->insert('genres');
$genres[] = $genre['name'];
}
// Update link table
// Get id of genre to put in link table
$flipped_genres = array_flip($genres);
$insert_array = [
'hummingbird_id' => $anime['id'],
'genre_id' => $flipped_genres[$genre['name']]
];
if (array_key_exists($anime['id'], $links))
{
if ( ! in_array($flipped_genres[$genre['name']], $links[$anime['id']]))
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
else
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
}
/**
* Get list of existing genres
*
* @return array
*/
private function get_genre_data()
{ {
$genres = []; $genres = [];
$flipped_genres = [];
$links = []; $links = [];
// Get existing genres // Get existing genres
$query = $this->db->select('id, genre') $query = $this->db->select('id, genre')
->from('genres') ->from('genres')
->get(); ->get();
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $genre) foreach($query->fetchAll(\PDO::FETCH_ASSOC) as $genre)
{ {
$genres[$genre['id']] = $genre['genre']; $genres[$genre['id']] = $genre['genre'];
} }
@ -146,7 +331,7 @@ class AnimeCollectionModel extends BaseDBModel {
$query = $this->db->select('hummingbird_id, genre_id') $query = $this->db->select('hummingbird_id, genre_id')
->from('genre_anime_set_link') ->from('genre_anime_set_link')
->get(); ->get();
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $link) foreach($query->fetchAll(\PDO::FETCH_ASSOC) as $link)
{ {
if (array_key_exists($link['hummingbird_id'], $links)) if (array_key_exists($link['hummingbird_id'], $links))
{ {
@ -158,48 +343,25 @@ class AnimeCollectionModel extends BaseDBModel {
} }
} }
return [
'genres' => $genres,
'links' => $links
];
}
/**
* Update genre information for the entire collection
*
* @return void
*/
private function update_genres()
{
// Get the anime collection // Get the anime collection
$collection = $this->_get_collection(); $collection = $this->_get_collection();
foreach($collection as $anime) foreach($collection as $anime)
{ {
// Get api information // Get api information
$api = $this->anime_model->get_anime($anime['hummingbird_id']); $this->update_genre($anime['hummingbird_id']);
foreach($api['genres'] as $genre)
{
// Add genres that don't currently exist
if ( ! in_array($genre['name'], $genres))
{
$this->db->set('genre', $genre['name'])
->insert('genres');
$genres[] = $genre['name'];
}
// Update link table
// Get id of genre to put in link table
$flipped_genres = array_flip($genres);
$insert_array = [
'hummingbird_id' => $anime['hummingbird_id'],
'genre_id' => $flipped_genres[$genre['name']]
];
if (array_key_exists($anime['hummingbird_id'], $links))
{
if ( ! in_array($flipped_genres[$genre['name']], $links[$anime['hummingbird_id']]))
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
else
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
} }
} }
} }

View File

@ -157,7 +157,7 @@ class AnimeModel extends BaseApiModel {
if ($response->getStatusCode() != 200) if ($response->getStatusCode() != 200)
{ {
throw new Exception($response->getEffectiveUrl()); throw new RuntimeException($response->getEffectiveUrl());
} }
return $response->json(); return $response->json();
@ -192,7 +192,7 @@ class AnimeModel extends BaseApiModel {
{ {
if ( ! file_exists($cache_file)) if ( ! file_exists($cache_file))
{ {
throw new Exception($response->getEffectiveUrl()); throw new DomainException($response->getEffectiveUrl());
} }
else else
{ {

View File

@ -101,7 +101,7 @@ class MangaModel extends BaseApiModel {
{ {
if ( ! file_exists($cache_file)) if ( ! file_exists($cache_file))
{ {
throw new Exception($response->getEffectiveUrl()); throw new DomainException($response->getEffectiveUrl());
} }
else else
{ {
@ -124,7 +124,7 @@ class MangaModel extends BaseApiModel {
} }
// Bail out early if there isn't any manga data // Bail out early if there isn't any manga data
if (empty($raw_data)) return []; if ( ! array_key_exists('manga', $raw_data)) return [];
$data = [ $data = [
'Reading' => [], 'Reading' => [],
@ -174,8 +174,6 @@ class MangaModel extends BaseApiModel {
} }
} }
//file_put_contents(_dir($this->config->data_cache_path, "manga-processed.json"), json_encode($data, JSON_PRETTY_PRINT));
return (array_key_exists($status, $data)) ? $data[$status] : $data; return (array_key_exists($status, $data)) ? $data[$status] : $data;
} }

View File

@ -1,15 +1,22 @@
<main> <main>
<?php if (is_logged_in()): ?>
[<a href="<?= $config->full_url('collection/add', 'anime') ?>">Add Item</a>]
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<section class="status"> <section class="status">
<h2><?= $name ?></h2> <h2><?= $name ?></h2>
<section class="media-wrap"> <section class="media-wrap">
<?php foreach($items as $item): ?> <?php foreach($items as $item): ?>
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<article class="media" id="a-<?= $item['hummingbird_id'] ?>"> <article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<img src="<?= $item['cover_image'] ?>" /> <img src="<?= $config->asset_url('images', 'anime', basename($item['cover_image'])) ?>" />
<div class="name"> <div class="name">
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<?= $item['title'] ?> <?= $item['title'] ?>
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?> <?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
</a>
</div> </div>
<div class="table"> <div class="table">
<div class="row"> <div class="row">
@ -18,11 +25,14 @@
<div class="age_rating"><?= $item['age_rating'] ?></div> <div class="age_rating"><?= $item['age_rating'] ?></div>
</div> </div>
</div> </div>
</article> </article>
</a>
<?php if (is_logged_in()): ?>
<span>[<a href="<?= $config->full_url("collection/edit/{$item['hummingbird_id']}", "anime") ?>">Edit</a>]</span>
<?php endif ?>
<?php endforeach ?> <?php endforeach ?>
</section> </section>
</section> </section>
<?php endforeach ?> <?php endforeach ?>
<?php endif ?>
</main> </main>

View File

@ -0,0 +1,38 @@
<?php if (is_logged_in()): ?>
<main>
<form action="<?= $action_url ?>" method="post">
<dl>
<dt>Series</dt>
<dd><label>Search for anime name: <input type="search" id="search" /></label></dd>
<dd>
<section id="series_list" class="media-wrap">
</section>
</dd>
<dt><label for="media_id">Media</label></dt>
<dd>
<select name="media_id" id="media_id">
<?php foreach($media_items as $id => $name): ?>
<option value="<?= $id ?>"><?= $name ?></option>
<?php endforeach ?>
</select>
</dd>
<dt><label for="notes">Notes</label></dt>
<dd><textarea id="notes" name="notes"></textarea></dd>
<dt>&nbsp;</dt>
<dd>
<button type="submit">Save</button>
</dd>
</dl>
</form>
</main>
<template id="show_list">
<article class="media">
<div class="name"><label><input type="radio" name="id" value="{{:id}}" />&nbsp;<span>{{:title}}<br />{{:alternate_title}}</span></label></div>
<img src="{{:cover_image}}" alt="{{:title}}" />
</article>
</template>
<script src="<?= $config->asset_url('js.php?g=collection&debug=1') ?>"></script>
<?php endif ?>

View File

@ -0,0 +1,37 @@
<?php if (is_logged_in()): ?>
<main>
<form action="<?= $action_url ?>" method="post">
<dl>
<h2><?= $item['title'] ?></h2>
<h3><?= $item['alternate_title'] ?></h3>
<dt><label for="media_id">Media</label></dt>
<dd>
<select name="media_id" id="media_id">
<?php foreach($media_items as $id => $name): ?>
<option <?= $item['media_id'] == $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
<?php endforeach ?>
</select>
</dd>
<dt><label for="notes">Notes</label></dt>
<dd><textarea id="notes" name="notes"><?= $item['notes'] ?></textarea></dd>
<dt>&nbsp;</dt>
<dd>
<?php if($action === 'Edit'): ?>
<input type="hidden" name="hummingbird_id" value="<?= $item['hummingbird_id'] ?>" />
<?php endif ?>
<button type="submit">Save</button>
</dd>
</dl>
</form>
</main>
<template id="show_list">
<article class="media">
<div class="name"><label><input type="radio" name="id" value="{{:id}}" />&nbsp;<span>{{:title}}<br />{{:alternate_title}}</span></label></div>
<img src="{{:cover_image}}" alt="{{:title}}" />
</article>
</template>
<script src="<?= $config->asset_url('js.php?g=collection&debug=1') ?>"></script>
<?php endif ?>

View File

@ -1,16 +1,25 @@
<main> <main>
<?php if (is_logged_in()): ?>
[<a href="<?= $config->full_url('collection/add', 'anime') ?>">Add Item</a>]
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2> <h2><?= $name ?></h2>
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Title</th> <th>Title</th>
<th>Alternate Title</th> <?php /*<th>Alternate Title</th>*/ ?>
<th>Episode Count</th> <th>Episode Count</th>
<th>Episode Length</th> <th>Episode Length</th>
<th>Show Type</th> <th>Show Type</th>
<th>Age Rating</th> <th>Age Rating</th>
<th>Notes</th> <th>Notes</th>
<?php if (is_logged_in()): ?>
<th>&nbsp;</th>
<?php endif ?>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -20,24 +29,28 @@
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>"> <a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<?= $item['title'] ?> <?= $item['title'] ?>
</a> </a>
<?= ( ! empty($item['alternate_title'])) ? " &middot; " . $item['alternate_title'] : "" ?>
</td> </td>
<td class="align_left"><?= $item['alternate_title'] ?></td> <?php /*<td class="align_left">
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<?= $item['title'] ?>
</a>
</td>
<td class="align_left"><?= $item['alternate_title'] ?></td>*/ ?>
<td><?= $item['episode_count'] ?></td> <td><?= $item['episode_count'] ?></td>
<td><?= $item['episode_length'] ?></td> <td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td> <td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td> <td><?= $item['age_rating'] ?></td>
<td class="align_left"><?= $item['notes'] ?></td> <td class="align_left"><?= $item['notes'] ?></td>
<?php if (is_logged_in()): ?>
<td>[<a href="<?= $config->full_url("collection/edit/{$item['hummingbird_id']}", "anime") ?>">Edit</a>]</td>
<?php endif ?>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
<br /> <br />
<?php endforeach ?> <?php endforeach ?>
<?php endif ?>
</main> </main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <script src="<?= $config->asset_url('js.php?g=table') ?>"></script>
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
<script>
$(function() {
$('table').tablesorter();
});
</script>

View File

@ -1,4 +1,7 @@
<main> <main>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<section class="status"> <section class="status">
<h2><?= $name ?></h2> <h2><?= $name ?></h2>
@ -34,7 +37,8 @@
</section> </section>
</section> </section>
<?php endforeach ?> <?php endforeach ?>
<?php endif ?>
</main> </main>
<?php if (is_logged_in()): ?> <?php if (is_logged_in()): ?>
<script src="<?= asset_url('js.php?g=edit') ?>"></script> <script src="<?= $config->asset_url('js.php?g=edit') ?>"></script>
<?php endif ?> <?php endif ?>

View File

@ -1,4 +1,7 @@
<main> <main>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2> <h2><?= $name ?></h2>
<table> <table>
@ -11,6 +14,7 @@
<th>Type</th> <th>Type</th>
<th>Progress</th> <th>Progress</th>
<th>Rated</th> <th>Rated</th>
<th>Genres</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -27,10 +31,17 @@
<td><?= $item['anime']['show_type'] ?></td> <td><?= $item['anime']['show_type'] ?></td>
<td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td> <td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td>
<td><?= $item['anime']['age_rating'] ?></td> <td><?= $item['anime']['age_rating'] ?></td>
<td class="flex flex-justify-space-around align-left">
<?php sort($item['anime']['genres']) ?>
<?php foreach($item['anime']['genres'] as $genre): ?>
<span><?= $genre['name'] ?></span>
<?php endforeach ?>
</td>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
<?php endforeach ?> <?php endforeach ?>
<?php endif ?>
</main> </main>
<script src="<?= asset_url('js.php?g=table') ?>"></script> <script src="<?= $config->asset_url('js.php?g=table') ?>"></script>

View File

@ -3,24 +3,24 @@
<head> <head>
<title><?= $title ?></title> <title><?= $title ?></title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="<?= asset_url('css.php?g=base') ?>" /> <link rel="stylesheet" href="<?= $config->asset_url('css.php?g=base') ?>" />
<script> <script>
var BASE_URL = "<?= base_url($url_type) ?>"; var BASE_URL = "<?= $config->base_url($url_type) ?>";
var CONTROLLER = "<?= $url_type ?>"; var CONTROLLER = "<?= $url_type ?>";
</script> </script>
</head> </head>
<body class="<?= $url_type ?> list"> <body class="<?= $url_type ?> list">
<h1 class="flex flex-align-end flex-wrap"> <h1 class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1"> <span class="flex-no-wrap grow-1">
<a href="<?= full_url("", $url_type) ?>"> <a href="<?= $config->full_url("", $url_type) ?>">
<?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> <?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?>
</a> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>] </a> [<a href="<?= $config->full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
</span> </span>
<span class="flex-no-wrap small-font"> <span class="flex-no-wrap small-font">
<?php if (is_logged_in()): ?> <?php if (is_logged_in()): ?>
[<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>] [<a href="<?= $config->full_url("/logout", $url_type) ?>">Logout</a>]
<?php else: ?> <?php else: ?>
[<a href="<?= full_url("/login", $url_type) ?>"><?= WHOSE ?> Login</a>] [<a href="<?= $config->full_url("/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
<?php endif ?> <?php endif ?>
</span> </span>
</h1> </h1>
@ -28,14 +28,16 @@
<nav> <nav>
<ul> <ul>
<?php foreach($nav_routes as $title => $nav_path): ?> <?php foreach($nav_routes as $title => $nav_path): ?>
<li class="<?= is_selected($nav_path, $route_path) ?>"><a href="<?= full_url($nav_path, $url_type) ?>"><?= $title ?></a></li> <li class="<?= is_selected($nav_path, $route_path) ?>"><a href="<?= $config->full_url($nav_path, $url_type) ?>"><?= $title ?></a></li>
<?php endforeach ?> <?php endforeach ?>
</ul> </ul>
<?php if (is_view_page()): ?>
<br /> <br />
<ul> <ul>
<li class="<?= is_not_selected('list', last_segment()) ?>"><a href="<?= full_url($route_path, $url_type) ?>">Cover View</a></li> <li class="<?= is_not_selected('list', last_segment()) ?>"><a href="<?= $config->full_url($route_path, $url_type) ?>">Cover View</a></li>
<li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= full_url("{$route_path}/list", $url_type) ?>">List View</a></li> <li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= $config->full_url("{$route_path}/list", $url_type) ?>">List View</a></li>
</ul> </ul>
<?php endif ?>
</nav> </nav>
<br /> <br />
<?php endif ?> <?php endif ?>

View File

@ -1,7 +1,7 @@
<main> <main>
<?= $message ?> <?= $message ?>
<aside> <aside>
<form method="post" action="<?= full_url('/login', $url_type) ?>"> <form method="post" action="<?= $config->full_url('/login', $url_type) ?>">
<dl> <dl>
<?php /*<dt><label for="username">Username: </label></dt> <?php /*<dt><label for="username">Username: </label></dt>
<dd><input type="text" id="username" name="username" required="required" /></dd>*/ ?> <dd><input type="text" id="username" name="username" required="required" /></dd>*/ ?>
@ -10,7 +10,7 @@
<dd><input type="password" id="password" name="password" required="required" /></dd> <dd><input type="password" id="password" name="password" required="required" /></dd>
<dt>&nbsp;</dt> <dt>&nbsp;</dt>
<dd><input type="submit" value="Login" /></dd> <dd><button type="submit">Login</button></dd>
</dl> </dl>
</form> </form>
</aside> </aside>

View File

@ -1,4 +1,7 @@
<main> <main>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<section class="status"> <section class="status">
<h2><?= $name ?></h2> <h2><?= $name ?></h2>
@ -43,7 +46,8 @@
</section> </section>
</section> </section>
<?php endforeach ?> <?php endforeach ?>
<?php endif ?>
</main> </main>
<?php if (is_logged_in()): ?> <?php if (is_logged_in()): ?>
<script src="<?= asset_url('js.php?g=edit') ?>"></script> <script src="<?= $config->asset_url('js.php?g=edit') ?>"></script>
<?php endif ?> <?php endif ?>

View File

@ -1,4 +1,7 @@
<main> <main>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2> <h2><?= $name ?></h2>
<table> <table>
@ -29,5 +32,6 @@
</tbody> </tbody>
</table> </table>
<?php endforeach ?> <?php endforeach ?>
<?php endif ?>
</main> </main>
<script src="<?= asset_url('js.php?g=table') ?>"></script> <script src="<?= $config->asset_url('js.php?g=table') ?>"></script>

View File

@ -1,3 +1,7 @@
template {
display: none;
}
body { body {
margin: 0.5em; margin: 0.5em;
} }
@ -35,8 +39,22 @@ tbody > tr:nth-child(odd) {
align-items: flex-end; align-items: flex-end;
} }
.flex-align-space-around {
-webkit-align-content: space-around;
-ms-flex-line-pack: distribute;
align-content: space-around;
}
.flex-justify-space-around { .flex-justify-space-around {
jusify-content: space-around; -webkit-justify-content: space-around;
-ms-flex-pack: distribute;
justify-content: space-around;
}
.flex-self-center {
-webkit-align-self: center;
-ms-flex-item-align: center;
align-self: center;
} }
.flex { .flex {
@ -49,6 +67,10 @@ tbody > tr:nth-child(odd) {
font-size: 1.6rem; font-size: 1.6rem;
} }
.align_center {
text-align: center;
}
.align_left { .align_left {
text-align: left; text-align: left;
} }
@ -57,22 +79,6 @@ tbody > tr:nth-child(odd) {
text-align: right; text-align: right;
} }
.round_all {
border-radius: 0.5em;
}
.round_top {
border-radius: 0;
border-top-right-radius: 0.5em;
border-top-left-radius: 0.5em;
}
.round_bottom {
border-radius: 0;
border-bottom-right-radius: 0.5em;
border-bottom-left-radius: 0.5em;
}
.media-wrap { .media-wrap {
text-align: center; text-align: center;
margin: 0 auto; margin: 0 auto;

View File

@ -3,12 +3,11 @@
--title-overlay: rgba(0, 0, 0, 0.45); --title-overlay: rgba(0, 0, 0, 0.45);
--text-color: #ffffff; --text-color: #ffffff;
--normal-padding: 0.25em; --normal-padding: 0.25em;
--radius: 0.5em;
} }
body { template {display:none}
margin: 0.5em;
} body {margin: 0.5em;}
table { table {
width:85%; width:85%;
@ -23,36 +22,18 @@ tbody > tr:nth-child(odd) {
.flex-wrap {flex-wrap: wrap} .flex-wrap {flex-wrap: wrap}
.flex-no-wrap {flex-wrap: nowrap} .flex-no-wrap {flex-wrap: nowrap}
.flex-align-end {align-items: flex-end} .flex-align-end {align-items: flex-end}
.flex-justify-space-around {jusify-content: space-around} .flex-align-space-around {align-content: space-around}
.flex-justify-space-around {justify-content: space-around}
.flex-self-center {align-self:center}
.flex {display: flex} .flex {display: flex}
.small-font { .small-font {
font-size:1.6rem; font-size:1.6rem;
} }
.align_left { .align_center {text-align:center}
text-align:left; .align_left {text-align:left;}
} .align_right {text-align:right;}
.align_right {
text-align:right;
}
.round_all {
border-radius:var(--radius);
}
.round_top {
border-radius: 0;
border-top-right-radius:var(--radius);
border-top-left-radius:var(--radius);
}
.round_bottom {
border-radius: 0;
border-bottom-right-radius:var(--radius);
border-bottom-left-radius:var(--radius);
}
.media-wrap { .media-wrap {
text-align:center; text-align:center;

View File

@ -40,7 +40,7 @@ function get_files()
foreach($groups[$_GET['g']] as $file) foreach($groups[$_GET['g']] as $file)
{ {
$new_file = realpath($js_root.$file); $new_file = realpath($js_root.$file);
$js .= file_get_contents($new_file); $js .= file_get_contents($new_file) . "\n\n";
} }
return $js; return $js;
@ -57,14 +57,32 @@ function get_files()
*/ */
function google_min($new_file) function google_min($new_file)
{ {
$options = [
'output_info' => 'compiled_code',
'output_format' => 'json',
'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
'js_code' => urlencode($new_file),
'warning_level' => 'QUIET',
'language' => 'ECMASCRIPT5'
];
$option_pairs = [];
foreach($options as $key => $value)
{
$option_pairs[] = "{$key}={$value}";
}
$option_string = implode("&", $option_pairs);
//Get a much-minified version from Google's closure compiler //Get a much-minified version from Google's closure compiler
$ch = curl_init('http://closure-compiler.appspot.com/compile'); $ch = curl_init('http://closure-compiler.appspot.com/compile');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'output_info=compiled_code&output_format=text&compilation_level=SIMPLE_OPTIMIZATIONS&js_code=' . urlencode($new_file)); curl_setopt($ch, CURLOPT_POSTFIELDS, $option_string);
$output = curl_exec($ch); $json = curl_exec($ch);
curl_close($ch); curl_close($ch);
return $output;
$obj = json_decode($json);
return $obj->compiledCode;
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
@ -146,7 +164,12 @@ if($last_modified === $requested_time)
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
//Determine what to do: rebuild cache, send files as is, or send cache. //Determine what to do: rebuild cache, send files as is, or send cache.
if($cache_modified < $last_modified) // If debug is set, just concatenate
if(isset($_GET['debug']))
{
$js = get_files();
}
else if($cache_modified < $last_modified)
{ {
$js = google_min(get_files()); $js = google_min(get_files());
@ -156,11 +179,6 @@ if($cache_modified < $last_modified)
die("Cache file was not created. Make sure you have the correct folder permissions."); die("Cache file was not created. Make sure you have the correct folder permissions.");
} }
} }
// If debug is set, just concatenate
else if(isset($_GET['debug']))
{
$js = get_files();
}
// Otherwise, send the cached file // Otherwise, send the cached file
else else
{ {

View File

@ -9,6 +9,7 @@
$(".media button.plus_one").on("click", function(e) { $(".media button.plus_one").on("click", function(e) {
e.stopPropagation(); e.stopPropagation();
var self = this;
var this_sel = $(this); var this_sel = $(this);
var parent_sel = $(this).closest("article"); var parent_sel = $(this).closest("article");
var self = this; var self = this;
@ -41,7 +42,7 @@
$.post(BASE_URL + 'update', data, function(res) { $.post(BASE_URL + 'update', data, function(res) {
if (res.status === 'completed') if (res.status === 'completed')
{ {
this_sel.parent('article').hide(); $(self).closest('article').hide();
} }
add_message('success', "Sucessfully updated " + title); add_message('success', "Sucessfully updated " + title);

17
public/js/collection.js Normal file
View File

@ -0,0 +1,17 @@
(function($, undefined) {
function search(query, callback)
{
$.get(BASE_URL + 'search', {'query':query}, callback);
}
$("#search").on('keypress', $.throttle(250, function(e) {
var query = encodeURIComponent($(this).val());
search(query, function(res) {
var template = $.templates("#show_list");
var html = template.render(res);
$('#series_list').html(html);
});
}));
}(jQuery));

View File

@ -0,0 +1,252 @@
/*!
* jQuery throttle / debounce - v1.1 - 3/7/2010
* http://benalman.com/projects/jquery-throttle-debounce-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
// Script: jQuery throttle / debounce: Sometimes, less is more!
//
// *Version: 1.1, Last updated: 3/7/2010*
//
// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/
// GitHub - http://github.com/cowboy/jquery-throttle-debounce/
// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js
// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb)
//
// About: License
//
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
//
// About: Examples
//
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
//
// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/
// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/
//
// About: Support and Testing
//
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
//
// jQuery Versions - none, 1.3.2, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1.
// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/
//
// About: Release History
//
// 1.1 - (3/7/2010) Fixed a bug in <jQuery.throttle> where trailing callbacks
// executed later than they should. Reworked a fair amount of internal
// logic as well.
// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over
// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the
// no_trailing throttle parameter and debounce functionality.
//
// Topic: Note for non-jQuery users
//
// jQuery isn't actually required for this plugin, because nothing internal
// uses any jQuery methods or properties. jQuery is just used as a namespace
// under which these methods can exist.
//
// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist
// when this plugin is loaded, the method described below will be created in
// the `Cowboy` namespace. Usage will be exactly the same, but instead of
// $.method() or jQuery.method(), you'll need to use Cowboy.method().
(function(window,undefined){
'$:nomunge'; // Used by YUI compressor.
// Since jQuery really isn't required for this plugin, use `jQuery` as the
// namespace only if it already exists, otherwise use the `Cowboy` namespace,
// creating it if necessary.
var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ),
// Internal method reference.
jq_throttle;
// Method: jQuery.throttle
//
// Throttle execution of a function. Especially useful for rate limiting
// execution of handlers on events like resize and scroll. If you want to
// rate-limit execution of a function to a single time, see the
// <jQuery.debounce> method.
//
// In this visualization, | is a throttled-function call and X is the actual
// callback execution:
//
// > Throttled with `no_trailing` specified as false or unspecified:
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
// > X X X X X X X X X X X X
// >
// > Throttled with `no_trailing` specified as true:
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
// > X X X X X X X X X X
//
// Usage:
//
// > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback );
// >
// > jQuery('selector').bind( 'someevent', throttled );
// > jQuery('selector').unbind( 'someevent', throttled );
//
// This also works in jQuery 1.4+:
//
// > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) );
// > jQuery('selector').unbind( 'someevent', callback );
//
// Arguments:
//
// delay - (Number) A zero-or-greater delay in milliseconds. For event
// callbacks, values around 100 or 250 (or even higher) are most useful.
// no_trailing - (Boolean) Optional, defaults to false. If no_trailing is
// true, callback will only execute every `delay` milliseconds while the
// throttled-function is being called. If no_trailing is false or
// unspecified, callback will be executed one final time after the last
// throttled-function call. (After the throttled-function has not been
// called for `delay` milliseconds, the internal counter is reset)
// callback - (Function) A function to be executed after delay milliseconds.
// The `this` context and all arguments are passed through, as-is, to
// `callback` when the throttled-function is executed.
//
// Returns:
//
// (Function) A new, throttled, function.
$.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) {
// After wrapper has stopped being called, this timeout ensures that
// `callback` is executed at the proper times in `throttle` and `end`
// debounce modes.
var timeout_id,
// Keep track of the last time `callback` was executed.
last_exec = 0;
// `no_trailing` defaults to falsy.
if ( typeof no_trailing !== 'boolean' ) {
debounce_mode = callback;
callback = no_trailing;
no_trailing = undefined;
}
// The `wrapper` function encapsulates all of the throttling / debouncing
// functionality and when executed will limit the rate at which `callback`
// is executed.
function wrapper() {
var that = this,
elapsed = +new Date() - last_exec,
args = arguments;
// Execute `callback` and update the `last_exec` timestamp.
function exec() {
last_exec = +new Date();
callback.apply( that, args );
};
// If `debounce_mode` is true (at_begin) this is used to clear the flag
// to allow future `callback` executions.
function clear() {
timeout_id = undefined;
};
if ( debounce_mode && !timeout_id ) {
// Since `wrapper` is being called for the first time and
// `debounce_mode` is true (at_begin), execute `callback`.
exec();
}
// Clear any existing timeout.
timeout_id && clearTimeout( timeout_id );
if ( debounce_mode === undefined && elapsed > delay ) {
// In throttle mode, if `delay` time has been exceeded, execute
// `callback`.
exec();
} else if ( no_trailing !== true ) {
// In trailing throttle mode, since `delay` time has not been
// exceeded, schedule `callback` to execute `delay` ms after most
// recent execution.
//
// If `debounce_mode` is true (at_begin), schedule `clear` to execute
// after `delay` ms.
//
// If `debounce_mode` is false (at end), schedule `callback` to
// execute after `delay` ms.
timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay );
}
};
// Set the guid of `wrapper` function to the same of original callback, so
// it can be removed in jQuery 1.4+ .unbind or .die by using the original
// callback as a reference.
if ( $.guid ) {
wrapper.guid = callback.guid = callback.guid || $.guid++;
}
// Return the wrapper function.
return wrapper;
};
// Method: jQuery.debounce
//
// Debounce execution of a function. Debouncing, unlike throttling,
// guarantees that a function is only executed a single time, either at the
// very beginning of a series of calls, or at the very end. If you want to
// simply rate-limit execution of a function, see the <jQuery.throttle>
// method.
//
// In this visualization, | is a debounced-function call and X is the actual
// callback execution:
//
// > Debounced with `at_begin` specified as false or unspecified:
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
// > X X
// >
// > Debounced with `at_begin` specified as true:
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
// > X X
//
// Usage:
//
// > var debounced = jQuery.debounce( delay, [ at_begin, ] callback );
// >
// > jQuery('selector').bind( 'someevent', debounced );
// > jQuery('selector').unbind( 'someevent', debounced );
//
// This also works in jQuery 1.4+:
//
// > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) );
// > jQuery('selector').unbind( 'someevent', callback );
//
// Arguments:
//
// delay - (Number) A zero-or-greater delay in milliseconds. For event
// callbacks, values around 100 or 250 (or even higher) are most useful.
// at_begin - (Boolean) Optional, defaults to false. If at_begin is false or
// unspecified, callback will only be executed `delay` milliseconds after
// the last debounced-function call. If at_begin is true, callback will be
// executed only at the first debounced-function call. (After the
// throttled-function has not been called for `delay` milliseconds, the
// internal counter is reset)
// callback - (Function) A function to be executed after delay milliseconds.
// The `this` context and all arguments are passed through, as-is, to
// `callback` when the debounced-function is executed.
//
// Returns:
//
// (Function) A new, debounced, function.
$.debounce = function( delay, at_begin, callback ) {
return callback === undefined
? jq_throttle( delay, at_begin, false )
: jq_throttle( delay, callback, at_begin !== false );
};
})(this);

1958
public/js/lib/jsrender.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,8 @@ class ConfigTest extends AnimeClient_TestCase {
{ {
$this->config = new Config([ $this->config = new Config([
'config' => [ 'config' => [
'foo' => 'bar' 'foo' => 'bar',
'asset_path' => '//localhost/assets/'
], ],
'base_config' => [ 'base_config' => [
'bar' => 'baz' 'bar' => 'baz'
@ -18,7 +19,9 @@ class ConfigTest extends AnimeClient_TestCase {
public function testConfig__get() public function testConfig__get()
{ {
$this->assertEquals($this->config->foo, $this->config->__get('foo'));
$this->assertEquals($this->config->bar, $this->config->__get('bar')); $this->assertEquals($this->config->bar, $this->config->__get('bar'));
$this->assertEquals(NULL, $this->config->baz);
} }
public function testGetNonExistentConfigItem() public function testGetNonExistentConfigItem()
@ -26,4 +29,81 @@ class ConfigTest extends AnimeClient_TestCase {
$this->assertEquals(NULL, $this->config->foobar); $this->assertEquals(NULL, $this->config->foobar);
} }
public function assetUrlProvider()
{
return [
'single argument' => [
'args' => [
'images'
],
'expected' => '//localhost/assets/images',
],
'multiple arguments' => [
'args' => [
'images', 'anime', 'foo.png'
],
'expected' => '//localhost/assets/images/anime/foo.png'
]
];
}
/**
* @dataProvider assetUrlProvider
*/
public function testAssetUrl($args, $expected)
{
$result = call_user_func_array([$this->config, 'asset_url'], $args);
$this->assertEquals($expected, $result);
}
public function fullUrlProvider()
{
return [
'default_view' => [
'config' => [
'anime_host' => '',
'manga_host' => '',
'anime_path' => 'anime',
'manga_path' => 'manga',
'route_by' => 'host',
'default_list' => 'manga',
'default_anime_path' => '/watching',
'default_manga_path' => '/all',
'default_to_list_view' => FALSE,
],
'path' => '',
'type' => 'manga',
'expected' => '//localhost/manga/all',
],
'default_view_list' => [
'config' => [
'anime_host' => '',
'manga_host' => '',
'anime_path' => 'anime',
'manga_path' => 'manga',
'route_by' => 'host',
'default_list' => 'manga',
'default_anime_path' => '/watching',
'default_manga_path' => '/all',
'default_to_list_view' => TRUE,
],
'path' => '',
'type' => 'manga',
'expected' => '//localhost/manga/all/list',
]
];
}
/**
* @dataProvider fullUrlProvider
*/
public function testFullUrl($config, $path, $type, $expected)
{
$this->config = new Config(['config' => $config, 'base_config' => []]);
$result = $this->config->full_url($path, $type);
$this->assertEquals($expected, $result);
}
} }

View File

@ -4,21 +4,6 @@ use \AnimeClient\Config;
class FunctionsTest extends AnimeClient_TestCase { class FunctionsTest extends AnimeClient_TestCase {
public function setUp()
{
parent::setUp();
global $config;
$config = new Config([
'config' => [
'asset_path' => '//localhost/assets/'
],
'base_config' => [
]
]);
}
/** /**
* Basic sanity test for _dir function * Basic sanity test for _dir function
*/ */
@ -45,34 +30,6 @@ class FunctionsTest extends AnimeClient_TestCase {
$this->assertEquals('', is_not_selected('foo', 'foo')); $this->assertEquals('', is_not_selected('foo', 'foo'));
} }
public function assetUrlProvider()
{
return [
'single argument' => [
'args' => [
'images'
],
'expected' => '//localhost/assets/images',
],
'multiple arguments' => [
'args' => [
'images', 'anime', 'foo.png'
],
'expected' => '//localhost/assets/images/anime/foo.png'
]
];
}
/**
* @dataProvider assetUrlProvider
*/
public function testAssetUrl($args, $expected)
{
$result = call_user_func_array('asset_url', $args);
$this->assertEquals($expected, $result);
}
public function testIsLoggedIn() public function testIsLoggedIn()
{ {
$this->assertFalse(is_logged_in()); $this->assertFalse(is_logged_in());

View File

@ -34,6 +34,34 @@ class RouterTest extends AnimeClient_TestCase {
$this->assertTrue(is_object($this->router)); $this->assertTrue(is_object($this->router));
} }
protected function _set_up($config, $uri, $host)
{
$this->config = new Config($config);
// Set up the environment
$_SERVER = array_merge($_SERVER, [
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => $uri,
'HTTP_HOST' => $host,
'SERVER_NAME' => $host
]);
$router_factory = new RouterFactory();
$this->aura_router = $router_factory->newInstance();
// Create Request/Response Objects
$web_factory = new WebFactory([
'_GET' => [],
'_POST' => [],
'_COOKIE' => [],
'_SERVER' => $_SERVER,
'_FILES' => []
]);
$this->request = $web_factory->newRequest();
$this->response = $web_factory->newResponse();
$this->router = new Router($this->config, $this->aura_router, $this->request, $this->response);
}
public function RouteTestProvider() public function RouteTestProvider()
{ {
return [ return [
@ -45,6 +73,7 @@ class RouterTest extends AnimeClient_TestCase {
'anime_path' => 'anime', 'anime_path' => 'anime',
'manga_path' => 'manga', 'manga_path' => 'manga',
'route_by' => 'path', 'route_by' => 'path',
'default_list' => 'anime'
], ],
'base_config' => [] 'base_config' => []
), ),
@ -61,6 +90,7 @@ class RouterTest extends AnimeClient_TestCase {
'anime_path' => '', 'anime_path' => '',
'manga_path' => '', 'manga_path' => '',
'route_by' => 'host', 'route_by' => 'host',
'default_list' => 'anime'
], ],
'base_config' => [] 'base_config' => []
), ),
@ -77,6 +107,7 @@ class RouterTest extends AnimeClient_TestCase {
'anime_path' => 'anime', 'anime_path' => 'anime',
'manga_path' => 'manga', 'manga_path' => 'manga',
'route_by' => 'path', 'route_by' => 'path',
'default_list' => 'manga'
], ],
'base_config' => [ 'base_config' => [
'routes' => [] 'routes' => []
@ -95,6 +126,7 @@ class RouterTest extends AnimeClient_TestCase {
'anime_path' => '', 'anime_path' => '',
'manga_path' => '', 'manga_path' => '',
'route_by' => 'host', 'route_by' => 'host',
'default_list' => 'manga'
], ],
'base_config' => [] 'base_config' => []
), ),
@ -102,7 +134,7 @@ class RouterTest extends AnimeClient_TestCase {
'host' => 'anime.host.me', 'host' => 'anime.host.me',
'uri' => '/watching', 'uri' => '/watching',
'check_var' => 'anime_host' 'check_var' => 'anime_host'
) ),
]; ];
} }
@ -147,30 +179,7 @@ class RouterTest extends AnimeClient_TestCase {
] ]
]; ];
$this->config = new Config($config); $this->_set_up($config, $uri, $host);
// Set up the environment
$_SERVER = array_merge($_SERVER, [
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => $uri,
'HTTP_HOST' => $host,
'SERVER_NAME' => $host
]);
$router_factory = new RouterFactory();
$this->aura_router = $router_factory->newInstance();
// Create Request/Response Objects
$web_factory = new WebFactory([
'_GET' => [],
'_POST' => [],
'_COOKIE' => [],
'_SERVER' => $_SERVER,
'_FILES' => []
]);
$this->request = $web_factory->newRequest();
$this->response = $web_factory->newResponse();
$this->router = new Router($this->config, $this->aura_router, $this->request, $this->response);
// Check route setup // Check route setup
$this->assertEquals($config['base_config']['routes'], $this->config->routes); $this->assertEquals($config['base_config']['routes'], $this->config->routes);
@ -189,4 +198,53 @@ class RouterTest extends AnimeClient_TestCase {
$route = $this->router->get_route(); $route = $this->router->get_route();
$this->assertInstanceOf('Aura\\Router\\Route', $route, "Route is valid, and matched"); $this->assertInstanceOf('Aura\\Router\\Route', $route, "Route is valid, and matched");
} }
/*public function testDefaultRoute()
{
$config = [
'config' => [
'anime_host' => '',
'manga_host' => '',
'anime_path' => 'anime',
'manga_path' => 'manga',
'route_by' => 'host',
'default_list' => 'manga'
],
'base_config' => [
'routes' => [
'common' => [
'login_form' => [
'path' => '/login',
'action' => ['login'],
'verb' => 'get'
],
],
'anime' => [
'index' => [
'path' => '/',
'action' => ['redirect'],
'params' => [
'url' => '', // Determined by config
'code' => '301'
]
],
],
'manga' => [
'index' => [
'path' => '/',
'action' => ['redirect'],
'params' => [
'url' => '', // Determined by config
'code' => '301',
'type' => 'manga'
]
],
]
]
]
];
$this->_set_up($config, "/", "localhost");
$this->assertEquals($this->config->full_url('', 'manga'), $this->response->headers->get('location'));
}*/
} }

View File

@ -32,7 +32,9 @@ class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
global $config; global $config;
$this->config = new Config([ $this->config = new Config([
'config' => [], 'config' => [
'asset_path' => '//localhost/assets/'
],
'base_config' => [ 'base_config' => [
'databaase' => [], 'databaase' => [],
'routes' => [ 'routes' => [