feat(*): refactor dynamic vs. static structure and distinctions; make additional elements dynamic
This commit is contained in:
parent
7a0b808f24
commit
be0fd1451c
136 changed files with 987 additions and 960 deletions
|
@ -59,6 +59,10 @@ CMD bash -c "\
|
|||
echo \"${SSH_PRIVATE_KEY}\" > ~/.ssh/id_rsa && \
|
||||
chmod 600 ~/.ssh/id_rsa && \
|
||||
ssh-keyscan -H \"${SERVER_IP}\" >> ~/.ssh/known_hosts && \
|
||||
rsync -avz --delete dist/ root@\"${SERVER_IP}\":/var/www/coryd.dev/ && \
|
||||
rsync -avz --delete dist/ root@\"${SERVER_IP}\":/var/www/coryd.dev/public/ && \
|
||||
rsync -avz --delete vendor/ root@\"${SERVER_IP}\":/var/www/coryd.dev/vendor/ && \
|
||||
rsync -avz --delete app/ root@\"${SERVER_IP}\":/var/www/coryd.dev/app/ && \
|
||||
rsync -avz --delete composer.json root@\"${SERVER_IP}\":/var/www/coryd.dev/composer.json && \
|
||||
rsync -avz --delete bootstrap.php root@\"${SERVER_IP}\":/var/www/coryd.dev/bootstrap.php && \
|
||||
echo \"✅ Deployed successfully\" && \
|
||||
tail -f /dev/null"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
require __DIR__ . "/Utils/init.php";
|
||||
|
||||
use App\Classes\ApiHandler;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\ApiHandler;
|
||||
use GuzzleHttp\Client;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
use GuzzleHttp\Client;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\ApiHandler;
|
||||
use GuzzleHttp\Client;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require __DIR__ . '/../server/utils/init.php';
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
use GuzzleHttp\Client;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . '/../server/utils/init.php';
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
$class = $_GET['class'] ?? null;
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
|
||||
class LatestListenHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 60;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->initializeCache();
|
||||
}
|
||||
|
||||
public function handleRequest(): void
|
||||
{
|
||||
try {
|
||||
$cachedData = $this->cache ? $this->cache->get("latest_listen") : null;
|
||||
|
||||
if ($cachedData) {
|
||||
$this->sendResponse(json_decode($cachedData, true));
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->makeRequest("GET", "optimized_latest_listen?select=*");
|
||||
|
||||
if (!is_array($data) || empty($data[0])) {
|
||||
$this->sendResponse(["message" => "No recent tracks found"], 404);
|
||||
return;
|
||||
}
|
||||
|
||||
$latestListen = $this->formatLatestListen($data[0]);
|
||||
|
||||
if ($this->cache) $this->cache->set(
|
||||
"latest_listen",
|
||||
json_encode($latestListen),
|
||||
$this->cacheTTL
|
||||
);
|
||||
|
||||
$this->sendResponse($latestListen);
|
||||
} catch (\Exception $e) {
|
||||
error_log("LatestListenHandler Error: " . $e->getMessage());
|
||||
|
||||
$this->sendErrorResponse("Internal Server Error: " . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function formatLatestListen(array $latestListen): array
|
||||
{
|
||||
$emoji = $latestListen["artist_emoji"] ?? ($latestListen["genre_emoji"] ?? "🎧");
|
||||
$trackName = htmlspecialchars(
|
||||
$latestListen["track_name"] ?? "Unknown Track",
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$artistName = htmlspecialchars(
|
||||
$latestListen["artist_name"] ?? "Unknown Artist",
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$url = htmlspecialchars($latestListen["url"] ?? "/", ENT_QUOTES, "UTF-8");
|
||||
|
||||
return [
|
||||
"content" => sprintf(
|
||||
'%s %s by <a href="https://www.coryd.dev%s">%s</a>',
|
||||
$emoji,
|
||||
$trackName,
|
||||
$url,
|
||||
$artistName
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$handler = new LatestListenHandler();
|
||||
$handler->handleRequest();
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require __DIR__ . '/../server/utils/init.php';
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
|
||||
|
@ -20,7 +19,7 @@ class QueryHandler extends BaseHandler
|
|||
$referer = $_SERVER['HTTP_REFERER'] ?? '';
|
||||
$hostAllowed = fn($url) => in_array(parse_url($url, PHP_URL_HOST), $allowedHosts, true);
|
||||
|
||||
if (!$hostAllowed($origin) && !$hostAllowed($referer)) $this->sendErrorResponse("Forbidden — invalid origin", 403);
|
||||
if (!$hostAllowed($origin) && !$hostAllowed($referer)) $this->sendErrorResponse("Forbidden: invalid origin", 403);
|
||||
|
||||
$allowedSource = $origin ?: $referer;
|
||||
$scheme = parse_url($allowedSource, PHP_URL_SCHEME) ?? 'https';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require __DIR__ . "/Utils/init.php";
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\ApiHandler;
|
||||
use GuzzleHttp\Client;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require __DIR__ . '/../server/utils/init.php';
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\ApiHandler;
|
||||
use GuzzleHttp\Client;
|
||||
|
|
154
api/umami.php
154
api/umami.php
|
@ -1,95 +1,95 @@
|
|||
<?php
|
||||
|
||||
$umamiHost = 'https://stats.coryd.dev';
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$proxyPrefix = '/assets/scripts';
|
||||
$forwardPath = parse_url($requestUri, PHP_URL_PATH);
|
||||
$forwardPath = str_replace($proxyPrefix, '', $forwardPath);
|
||||
$targetUrl = $umamiHost . $forwardPath;
|
||||
$umamiHost = 'https://stats.coryd.dev';
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$proxyPrefix = '/assets/scripts';
|
||||
$forwardPath = parse_url($requestUri, PHP_URL_PATH);
|
||||
$forwardPath = str_replace($proxyPrefix, '', $forwardPath);
|
||||
$targetUrl = $umamiHost . $forwardPath;
|
||||
|
||||
if (isset($contentType) && preg_match('#^(application/json|text/|application/javascript)#', $contentType)) {
|
||||
ob_start('ob_gzhandler');
|
||||
} else {
|
||||
ob_start();
|
||||
}
|
||||
if (isset($contentType) && preg_match('#^(application/json|text/|application/javascript)#', $contentType)) {
|
||||
ob_start('ob_gzhandler');
|
||||
} else {
|
||||
ob_start();
|
||||
}
|
||||
|
||||
if ($method === 'GET' && preg_match('#^/utils\.js$#', $forwardPath)) {
|
||||
$remoteUrl = $umamiHost . '/script.js';
|
||||
$cacheKey = 'remote_stats_script';
|
||||
$ttl = 3600;
|
||||
$js = null;
|
||||
$code = 200;
|
||||
$redis = null;
|
||||
if ($method === 'GET' && preg_match('#^/utils\.js$#', $forwardPath)) {
|
||||
$remoteUrl = $umamiHost . '/script.js';
|
||||
$cacheKey = 'remote_stats_script';
|
||||
$ttl = 3600;
|
||||
$js = null;
|
||||
$code = 200;
|
||||
$redis = null;
|
||||
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
|
||||
if ($redis->exists($cacheKey)) $js = $redis->get($cacheKey);
|
||||
if ($redis->exists($cacheKey)) $js = $redis->get($cacheKey);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis unavailable: " . $e->getMessage());
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis unavailable: " . $e->getMessage());
|
||||
|
||||
if (!is_string($js)) {
|
||||
$ch = curl_init($remoteUrl);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
|
||||
$js = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if ($redis && $code === 200 && $js) $redis->setex($cacheKey, $ttl, $js);
|
||||
}
|
||||
|
||||
if (!is_string($js) || trim($js) === '') {
|
||||
$js = '// Failed to fetch remote script';
|
||||
$code = 502;
|
||||
}
|
||||
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/javascript; charset=UTF-8');
|
||||
echo $js;
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!is_string($js)) {
|
||||
$ch = curl_init($remoteUrl);
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
];
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
if (isset($_SERVER['HTTP_USER_AGENT'])) $headers[] = 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
$js = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$ch = curl_init($targetUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
curl_close($ch);
|
||||
if ($method === 'POST') {
|
||||
$body = file_get_contents('php://input');
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if ($redis && $code === 200 && $js) $redis->setex($cacheKey, $ttl, $js);
|
||||
if (strpos($forwardPath, '/api/send') === 0 && is_array($data)) $data['payload'] = array_merge($data['payload'] ?? [], [
|
||||
'ip' => $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
|
||||
]);
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
}
|
||||
|
||||
if (!is_string($js) || trim($js) === '') {
|
||||
$js = '// Failed to fetch remote script';
|
||||
$code = 502;
|
||||
}
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/javascript; charset=UTF-8');
|
||||
echo $js;
|
||||
exit;
|
||||
}
|
||||
curl_close($ch);
|
||||
http_response_code($httpCode);
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
];
|
||||
if ($contentType) header("Content-Type: $contentType");
|
||||
|
||||
if (isset($_SERVER['HTTP_USER_AGENT'])) $headers[] = 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
$ch = curl_init($targetUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
if ($method === 'POST') {
|
||||
$body = file_get_contents('php://input');
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if (strpos($forwardPath, '/api/send') === 0 && is_array($data)) $data['payload'] = array_merge($data['payload'] ?? [], [
|
||||
'ip' => $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
|
||||
]);
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
|
||||
curl_close($ch);
|
||||
http_response_code($httpCode);
|
||||
|
||||
if ($contentType) header("Content-Type: $contentType");
|
||||
|
||||
echo $response ?: '';
|
||||
echo $response ?: '';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
use App\Classes\ApiHandler;
|
||||
use GuzzleHttp\Client;
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
namespace App\Classes;
|
||||
|
||||
require __DIR__ . "/BaseHandler.php";
|
||||
require __DIR__ . '/../../server/utils/init.php';
|
||||
|
||||
abstract class ApiHandler extends BaseHandler
|
||||
{
|
||||
protected function ensureCliAccess(): void
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace App\Classes;
|
||||
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
|
69
app/Classes/LatestListenHandler.php
Normal file
69
app/Classes/LatestListenHandler.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
|
||||
class LatestListenHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 60;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->initializeCache();
|
||||
}
|
||||
|
||||
public function handleRequest(): void
|
||||
{
|
||||
$data = $this->getLatestListen();
|
||||
|
||||
if (!$data) {
|
||||
$this->sendResponse(["message" => "No recent tracks found"], 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendResponse($data);
|
||||
}
|
||||
|
||||
public function getLatestListen(): ?array
|
||||
{
|
||||
try {
|
||||
$cachedData = $this->cache ? $this->cache->get("latest_listen") : null;
|
||||
|
||||
if ($cachedData) return json_decode($cachedData, true);
|
||||
|
||||
$data = $this->makeRequest("GET", "optimized_latest_listen?select=*");
|
||||
|
||||
if (!is_array($data) || empty($data[0])) return null;
|
||||
|
||||
$latestListen = $this->formatLatestListen($data[0]);
|
||||
|
||||
if ($this->cache) $this->cache->set("latest_listen", json_encode($latestListen), $this->cacheTTL);
|
||||
|
||||
return $latestListen;
|
||||
} catch (\Exception $e) {
|
||||
error_log("LatestListenHandler::getLatestListen error: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function formatLatestListen(array $latestListen): array
|
||||
{
|
||||
$emoji = $latestListen["artist_emoji"] ?? ($latestListen["genre_emoji"] ?? "🎧");
|
||||
$trackName = htmlspecialchars($latestListen["track_name"] ?? "Unknown Track", ENT_QUOTES, "UTF-8");
|
||||
$artistName = htmlspecialchars($latestListen["artist_name"] ?? "Unknown Artist", ENT_QUOTES, "UTF-8");
|
||||
$url = htmlspecialchars($latestListen["url"] ?? "/", ENT_QUOTES, "UTF-8");
|
||||
|
||||
return [
|
||||
"content" => sprintf(
|
||||
'%s %s by <a href="%s">%s</a>',
|
||||
$emoji,
|
||||
$trackName,
|
||||
$url,
|
||||
$artistName
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
26
app/Classes/MusicDataHandler.php
Normal file
26
app/Classes/MusicDataHandler.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
class MusicDataHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 300;
|
||||
|
||||
public function getThisWeekData(): array
|
||||
{
|
||||
$cacheKey = 'music_week_data';
|
||||
$cached = $this->cache ? $this->cache->get($cacheKey) : null;
|
||||
|
||||
if ($cached) return json_decode($cached, true);
|
||||
|
||||
$response = $this->makeRequest('GET', 'optimized_week_music?select=*');
|
||||
$music = $response[0]['week_music'] ?? [];
|
||||
$music['total_tracks'] = $music['week_summary']['total_tracks'] ?? 0;
|
||||
$music['total_artists'] = $music['week_summary']['total_artists'] ?? 0;
|
||||
$music['total_albums'] = $music['week_summary']['total_albums'] ?? 0;
|
||||
|
||||
if ($this->cache) $this->cache->set($cacheKey, json_encode($music), $this->cacheTTL);
|
||||
|
||||
return $music;
|
||||
}
|
||||
}
|
|
@ -11,21 +11,18 @@ abstract class PageFetcher extends BaseHandler
|
|||
|
||||
protected function cacheGet(string $key): mixed
|
||||
{
|
||||
return $this->cache && $this->cache->exists($key)
|
||||
? json_decode($this->cache->get($key), true)
|
||||
: null;
|
||||
return $this->cache && $this->cache->exists($key) ? json_decode($this->cache->get($key), true) : null;
|
||||
}
|
||||
|
||||
protected function cacheSet(string $key, mixed $value, int $ttl = 3600): void
|
||||
protected function cacheSet(string $key, mixed $value, int $ttl = 300): void
|
||||
{
|
||||
if ($this->cache) {
|
||||
$this->cache->setex($key, $ttl, json_encode($value));
|
||||
}
|
||||
if ($this->cache) $this->cache->setex($key, $ttl, json_encode($value));
|
||||
}
|
||||
|
||||
protected function fetchSingleFromApi(string $endpoint, string $url): ?array
|
||||
{
|
||||
$data = $this->fetchFromApi($endpoint, "url=eq./{$url}");
|
||||
|
||||
return $data[0] ?? null;
|
||||
}
|
||||
|
||||
|
@ -39,6 +36,7 @@ abstract class PageFetcher extends BaseHandler
|
|||
if ($this->globals !== null) return $this->globals;
|
||||
|
||||
$fetcher = new GlobalsFetcher();
|
||||
|
||||
$this->globals = $fetcher->fetch();
|
||||
|
||||
return $this->globals;
|
36
app/Classes/RecentMediaHandler.php
Normal file
36
app/Classes/RecentMediaHandler.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
class RecentMediaHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 300;
|
||||
|
||||
public function getRecentMedia(): array
|
||||
{
|
||||
try {
|
||||
$cacheKey = 'recent_media';
|
||||
|
||||
if ($this->cache) {
|
||||
$cached = $this->cache->get($cacheKey);
|
||||
|
||||
if ($cached) return json_decode($cached, true);
|
||||
}
|
||||
|
||||
$response = $this->makeRequest("GET", "optimized_recent_media?select=*");
|
||||
$activity = $response[0]['recent_activity'] ?? [];
|
||||
$data = [
|
||||
'recentMusic' => $activity['recentMusic'] ?? [],
|
||||
'recentWatchedRead' => $activity['recentWatchedRead'] ?? [],
|
||||
];
|
||||
|
||||
if ($this->cache) $this->cache->set($cacheKey, json_encode($data), $this->cacheTTL);
|
||||
|
||||
return $data;
|
||||
} catch (\Exception $e) {
|
||||
error_log("RecentMediaHandler error: " . $e->getMessage());
|
||||
|
||||
return ['recentMusic' => [], 'recentWatchedRead' => []];
|
||||
}
|
||||
}
|
||||
}
|
22
app/utils/icons.php
Normal file
22
app/utils/icons.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('getTablerIcon')) {
|
||||
function getTablerIcon($iconName, $class = '', $size = 24)
|
||||
{
|
||||
$icons = [
|
||||
'arrow-left' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" /></svg>',
|
||||
'arrow-right' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M13 18l6 -6" /><path d="M13 6l6 6" /></svg>',
|
||||
'article' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>',
|
||||
'books' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-books"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M9 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M5 8h4" /><path d="M9 16h4" /><path d="M13.803 4.56l2.184 -.53c.562 -.135 1.133 .19 1.282 .732l3.695 13.418a1.02 1.02 0 0 1 -.634 1.219l-.133 .041l-2.184 .53c-.562 .135 -1.133 -.19 -1.282 -.732l-3.695 -13.418a1.02 1.02 0 0 1 .634 -1.219l.133 -.041z" /><path d="M14 9l4 -1" /><path d="M16 16l3.923 -.98" /></svg>',
|
||||
'device-tv-old' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-tv-old"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M16 3l-4 4l-4 -4" /><path d="M15 7v13" /><path d="M18 15v.01" /><path d="M18 12v.01" /></svg>',
|
||||
'headphones' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-headphones"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M15 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M4 15v-3a8 8 0 0 1 16 0v3" /></svg>',
|
||||
'movie' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M8 4l0 16" /><path d="M16 4l0 16" /><path d="M4 8l4 0" /><path d="M4 16l4 0" /><path d="M4 12l16 0" /><path d="M16 8l4 0" /><path d="M16 16l4 0" /></svg>',
|
||||
'star' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-star"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>',
|
||||
'vinyl' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-vinyl"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 3.937a9 9 0 1 0 5 8.063" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4l-3.5 10l-2.5 2" /></svg>'
|
||||
];
|
||||
|
||||
return $icons[$iconName] ?? '<span class="icon-placeholder">[Missing: ' . htmlspecialchars($iconName) . ']</span>';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
147
app/utils/media.php
Normal file
147
app/utils/media.php
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('renderMediaGrid')) {
|
||||
function renderMediaGrid(array $items, int $count = 0, string $loading = 'lazy')
|
||||
{
|
||||
$limit = $count > 0 ? $count : count($items);
|
||||
$firstType = $items[0]['type'] ?? ($items[0]['grid']['type'] ?? '');
|
||||
$shapeClass = in_array($firstType, ['books', 'movies', 'tv']) ? 'vertical' : 'square';
|
||||
|
||||
echo '<div class="media-grid ' . $shapeClass . '">';
|
||||
|
||||
foreach (array_slice($items, 0, $limit) as $item) {
|
||||
$grid = $item['grid'] ?? $item;
|
||||
$alt = htmlspecialchars($grid['alt'] ?? '');
|
||||
$image = htmlspecialchars($grid['image'] ?? '');
|
||||
$title = htmlspecialchars($grid['title'] ?? '');
|
||||
$subtext = htmlspecialchars($grid['subtext'] ?? '');
|
||||
$url = $grid['url'] ?? null;
|
||||
$type = $item['type'] ?? '';
|
||||
$isVertical = in_array($type, ['books', 'movies', 'tv']);
|
||||
$imageClass = $isVertical ? 'vertical' : 'square';
|
||||
$width = $isVertical ? 120 : 150;
|
||||
$height = $isVertical ? 184 : 150;
|
||||
|
||||
$openLink = $url ? '<a class="' . htmlspecialchars($type) . '" href="' . htmlspecialchars($url) . '" title="' . $alt . '">' : '';
|
||||
$closeLink = $url ? '</a>' : '';
|
||||
|
||||
echo $openLink;
|
||||
echo '<div class="media-grid-item">';
|
||||
|
||||
if ($title || $subtext) {
|
||||
echo '<div class="meta-text media-highlight">';
|
||||
if ($title) echo '<div class="header">' . $title . '</div>';
|
||||
if ($subtext) echo '<div class="subheader">' . $subtext . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '<img
|
||||
srcset="' . $image . '?class=' . $imageClass . 'sm&type=webp ' . $width . 'w, ' .
|
||||
$image . '?class=' . $imageClass . 'md&type=webp ' . ($width * 2) . 'w"
|
||||
sizes="(max-width: 450px) ' . $width . 'px, ' . ($width * 2) . 'px"
|
||||
src="' . $image . '?class=' . $imageClass . 'sm&type=webp"
|
||||
alt="' . $alt . '"
|
||||
loading="' . $loading . '"
|
||||
decoding="async"
|
||||
width="' . $width . '"
|
||||
height="' . $height . '"
|
||||
>';
|
||||
echo '</div>';
|
||||
echo $closeLink;
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('renderAssociatedMedia')) {
|
||||
function renderAssociatedMedia(
|
||||
array $artists = [],
|
||||
array $books = [],
|
||||
array $genres = [],
|
||||
array $movies = [],
|
||||
array $posts = [],
|
||||
array $shows = [],
|
||||
)
|
||||
{
|
||||
$sections = [
|
||||
"artists" => ["icon" => "headphones", "css_class" => "music", "label" => "Related artist(s)", "hasGrid" => true],
|
||||
"books" => ["icon" => "books", "css_class" => "books", "label" => "Related book(s)", "hasGrid" => true],
|
||||
"genres" => ["icon" => "headphones", "css_class" => "music", "label" => "Related genre(s)", "hasGrid" => false],
|
||||
"movies" => ["icon" => "movie", "css_class" => "movies", "label" => "Related movie(s)", "hasGrid" => true],
|
||||
"posts" => ["icon" => "article", "css_class" => "article", "label" => "Related post(s)", "hasGrid" => false],
|
||||
"shows" => ["icon" => "device-tv-old", "css_class" => "tv", "label" => "Related show(s)", "hasGrid" => true]
|
||||
];
|
||||
|
||||
$allMedia = compact('artists', 'books', 'genres', 'movies', 'posts', 'shows');
|
||||
|
||||
echo '<div class="associated-media">';
|
||||
|
||||
foreach ($sections as $key => $section) {
|
||||
$items = $allMedia[$key];
|
||||
|
||||
if (empty($items)) continue;
|
||||
|
||||
echo '<h3 id="' . htmlspecialchars($key) . '" class="' . htmlspecialchars($section['css_class']) . '">';
|
||||
echo getTablerIcon($section['icon']);
|
||||
echo htmlspecialchars($section['label']);
|
||||
echo '</h3>';
|
||||
|
||||
if ($section['hasGrid']) {
|
||||
renderMediaGrid($items);
|
||||
} else {
|
||||
echo '<ul>';
|
||||
foreach ($items as $item) {
|
||||
echo '<li class="' . htmlspecialchars($section['css_class']) . '">';
|
||||
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title'] ?? $item['name'] ?? '') . '</a>';
|
||||
|
||||
if ($key === "artists" && isset($item['total_plays']) && $item['total_plays'] > 0) {
|
||||
echo ' (' . htmlspecialchars($item['total_plays']) . ' play' . ($item['total_plays'] > 1 ? 's' : '') . ')';
|
||||
} elseif ($key === "books" && isset($item['author'])) {
|
||||
echo ' by ' . htmlspecialchars($item['author']);
|
||||
} elseif (($key === "movies" || $key === "shows") && isset($item['year'])) {
|
||||
echo ' (' . htmlspecialchars($item['year']) . ')';
|
||||
} elseif ($key === "posts" && isset($item['date'])) {
|
||||
echo ' (' . date("F j, Y", strtotime($item['date'])) . ')';
|
||||
}
|
||||
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('renderMediaLinks')) {
|
||||
function renderMediaLinks(array $data, string $type, int $count = 10): string {
|
||||
if (empty($data) || empty($type)) return "";
|
||||
|
||||
$slice = array_slice($data, 0, $count);
|
||||
|
||||
if (count($slice) === 0) return "";
|
||||
|
||||
$buildLink = function ($item) use ($type) {
|
||||
switch ($type) {
|
||||
case "genre":
|
||||
return '<a href="' . htmlspecialchars($item['genre_url']) . '">' . htmlspecialchars($item['genre_name']) . '</a>';
|
||||
case "artist":
|
||||
return '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['name']) . '</a>';
|
||||
case "book":
|
||||
return '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
if (count($slice) === 1) return $buildLink($slice[0]);
|
||||
|
||||
$links = array_map($buildLink, $slice);
|
||||
$last = array_pop($links);
|
||||
return implode(', ', $links) . ' and ' . $last;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
39
app/utils/metadata.php
Normal file
39
app/utils/metadata.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('setupPageMetadata')) {
|
||||
function setupPageMetadata(array $page, string $requestUri): array
|
||||
{
|
||||
$globals = $page['globals'] ?? [];
|
||||
|
||||
$title = htmlspecialchars($page["metadata"]["title"] ?? "", ENT_QUOTES, "UTF-8");
|
||||
$description = htmlspecialchars($page["metadata"]["description"] ?? "", ENT_QUOTES, "UTF-8");
|
||||
$image = htmlspecialchars(
|
||||
$page["metadata"]["open_graph_image"] ?? $globals["metadata"]["open_graph_image"] ?? "",
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$fullUrl = $globals["url"] . $requestUri;
|
||||
$oembedUrl = $globals["url"] . "/oembed" . $requestUri;
|
||||
|
||||
return [
|
||||
'pageTitle' => $title,
|
||||
'pageDescription' => $description,
|
||||
'ogImage' => $image,
|
||||
'fullUrl' => $fullUrl,
|
||||
'oembedUrl' => $oembedUrl,
|
||||
'globals' => $globals
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cleanMeta')) {
|
||||
function cleanMeta($value)
|
||||
{
|
||||
$value = trim($value ?? '');
|
||||
$value = str_replace(["\r", "\n"], ' ', $value);
|
||||
$value = preg_replace('/\s+/', ' ', $value);
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
41
app/utils/paginator.php
Normal file
41
app/utils/paginator.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('renderPaginator')) {
|
||||
function renderPaginator(array $pagination, int $totalPages): void
|
||||
{
|
||||
if (!$pagination || $totalPages <= 1) return;
|
||||
?>
|
||||
|
||||
<script type="module" src="/assets/scripts/components/select-pagination.js" defer></script>
|
||||
<nav aria-label="Pagination" class="pagination">
|
||||
<?php if (!empty($pagination['href']['previous'])): ?>
|
||||
<a href="<?= $pagination['href']['previous'] ?>" aria-label="Previous page">
|
||||
<?= getTablerIcon('arrow-left') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span><?= getTablerIcon('arrow-left') ?></span>
|
||||
<?php endif; ?>
|
||||
<select-pagination data-base-index="1">
|
||||
<select class="client-side" aria-label="Page selection">
|
||||
<?php foreach ($pagination['pages'] as $i): ?>
|
||||
<option value="<?= $i ?>" <?= ($pagination['pageNumber'] === $i) ? 'selected' : '' ?>>
|
||||
<?= $i ?> of <?= $totalPages ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<noscript>
|
||||
<p><span aria-current="page"><?= $pagination['pageNumber'] ?></span> of <?= $totalPages ?></p>
|
||||
</noscript>
|
||||
</select-pagination>
|
||||
<?php if (!empty($pagination['href']['next'])): ?>
|
||||
<a href="<?= $pagination['href']['next'] ?>" aria-label="Next page">
|
||||
<?= getTablerIcon('arrow-right') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span><?= getTablerIcon('arrow-right') ?></span>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
|
||||
<?php
|
||||
}
|
||||
}
|
11
app/utils/routing.php
Normal file
11
app/utils/routing.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('redirectTo404')) {
|
||||
function redirectTo404(): void
|
||||
{
|
||||
header("Location: /404/", true, 302);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
84
app/utils/strings.php
Normal file
84
app/utils/strings.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
use Kaoken\MarkdownIt\MarkdownIt;
|
||||
use Kaoken\MarkdownIt\Plugins\MarkdownItFootnote;
|
||||
|
||||
if (!function_exists('truncateText')) {
|
||||
function truncateText($text, $limit = 50, $ellipsis = "...")
|
||||
{
|
||||
if (mb_strwidth($text, "UTF-8") <= $limit) return $text;
|
||||
|
||||
$truncated = mb_substr($text, 0, $limit, "UTF-8");
|
||||
$lastSpace = mb_strrpos($truncated, " ", 0, "UTF-8");
|
||||
|
||||
if ($lastSpace !== false) $truncated = mb_substr($truncated, 0, $lastSpace, "UTF-8");
|
||||
|
||||
return $truncated . $ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('parseMarkdown')) {
|
||||
function parseMarkdown($markdown)
|
||||
{
|
||||
if (empty($markdown)) return '';
|
||||
|
||||
$md = new MarkdownIt([
|
||||
"html" => true,
|
||||
"linkify" => true,
|
||||
]);
|
||||
|
||||
$md->plugin(new MarkdownItFootnote());
|
||||
|
||||
return $md->render($markdown);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('parseCountryField')) {
|
||||
function parseCountryField($countryField)
|
||||
{
|
||||
if (empty($countryField)) return null;
|
||||
|
||||
$delimiters = [',', '/', '&', ' and '];
|
||||
$countries = [$countryField];
|
||||
|
||||
foreach ($delimiters as $delimiter) {
|
||||
$tempCountries = [];
|
||||
|
||||
foreach ($countries as $country) {
|
||||
$tempCountries = array_merge($tempCountries, explode($delimiter, $country));
|
||||
}
|
||||
|
||||
$countries = $tempCountries;
|
||||
}
|
||||
|
||||
$countries = array_map('trim', $countries);
|
||||
$countries = array_map('getCountryName', $countries);
|
||||
$countries = array_filter($countries);
|
||||
|
||||
return implode(', ', array_unique($countries));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getCountryName')) {
|
||||
function getCountryName($countryName)
|
||||
{
|
||||
$isoCodes = new \Sokil\IsoCodes\IsoCodesFactory();
|
||||
$countries = $isoCodes->getCountries();
|
||||
$country = $countries->getByAlpha2($countryName);
|
||||
|
||||
if ($country) return $country->getName();
|
||||
|
||||
return ucfirst(strtolower($countryName));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('pluralize')) {
|
||||
function pluralize($count, $string, $trailing = '')
|
||||
{
|
||||
if ((int)$count === 1) return $string;
|
||||
|
||||
return $string . 's' . ($trailing ? $trailing : '');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
17
app/utils/tags.php
Normal file
17
app/utils/tags.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('renderTags')) {
|
||||
function renderTags(array $tags): void
|
||||
{
|
||||
if (empty($tags)) return;
|
||||
|
||||
echo '<div class="tags">';
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$slug = strtolower(trim($tag));
|
||||
echo '<a href="/tags/' . rawurlencode($slug) . '">#' . htmlspecialchars($slug) . '</a>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
6
bootstrap.php
Normal file
6
bootstrap.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
define('PROJECT_ROOT', realpath(__DIR__ . '/..'));
|
||||
|
||||
require_once PROJECT_ROOT . '/vendor/autoload.php';
|
||||
require_once PROJECT_ROOT . '/app/utils/init.php';
|
64
cli/package-lock.json
generated
64
cli/package-lock.json
generated
|
@ -13,7 +13,7 @@
|
|||
"commander": "^14.0.0",
|
||||
"figlet": "^1.8.1",
|
||||
"fs-extra": "^11.3.0",
|
||||
"glob": "^11.0.2",
|
||||
"glob": "^11.0.3",
|
||||
"inquirer": "^12.6.3",
|
||||
"transliteration": "^2.3.5"
|
||||
},
|
||||
|
@ -337,6 +337,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/balanced-match": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/brace-expansion": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
|
@ -425,27 +446,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz",
|
||||
"integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.1.tgz",
|
||||
"integrity": "sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
|
@ -673,14 +673,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz",
|
||||
"integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==",
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
|
||||
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^4.0.1",
|
||||
"minimatch": "^10.0.0",
|
||||
"foreground-child": "^3.3.1",
|
||||
"jackspeak": "^4.1.1",
|
||||
"minimatch": "^10.0.3",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^2.0.0"
|
||||
|
@ -791,12 +791,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.2.tgz",
|
||||
"integrity": "sha512-+9TJCIYXgZ2Dm5LxVCFsa8jOm+evMwXHFI0JM1XROmkfkpz8/iLLDh+TwSmyIBrs6C6Xu9294/fq8cBA+P6AqA==",
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
|
||||
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^4.0.1"
|
||||
"@isaacs/brace-expansion": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"commander": "^14.0.0",
|
||||
"figlet": "^1.8.1",
|
||||
"fs-extra": "^11.3.0",
|
||||
"glob": "^11.0.2",
|
||||
"glob": "^11.0.3",
|
||||
"inquirer": "^12.6.3",
|
||||
"transliteration": "^2.3.5"
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\Classes\\": "api/Classes/"
|
||||
"App\\Classes\\": "app/Classes/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
|
|
|
@ -22,8 +22,8 @@ export default async function (eleventyConfig) {
|
|||
|
||||
eleventyConfig.addPassthroughCopy("src/assets");
|
||||
eleventyConfig.addPassthroughCopy("api");
|
||||
eleventyConfig.addPassthroughCopy("vendor");
|
||||
eleventyConfig.addPassthroughCopy("server");
|
||||
eleventyConfig.addPassthroughCopy("bootstrap.php");
|
||||
eleventyConfig.addPassthroughCopy("config/dynamic");
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
"node_modules/minisearch/dist/umd/index.js":
|
||||
"assets/scripts/components/minisearch.js",
|
||||
|
|
82
package-lock.json
generated
82
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "coryd.dev",
|
||||
"version": "9.2.9",
|
||||
"version": "10.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "coryd.dev",
|
||||
"version": "9.2.9",
|
||||
"version": "10.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minisearch": "^7.1.2",
|
||||
|
@ -251,6 +251,29 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@isaacs/balanced-match": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/brace-expansion": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
|
@ -756,9 +779,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001722",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz",
|
||||
"integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==",
|
||||
"version": "1.0.30001723",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
|
||||
"integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1385,9 +1408,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.166",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz",
|
||||
"integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==",
|
||||
"version": "1.5.167",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz",
|
||||
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
@ -1720,15 +1743,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz",
|
||||
"integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==",
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
|
||||
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^4.0.1",
|
||||
"minimatch": "^10.0.0",
|
||||
"foreground-child": "^3.3.1",
|
||||
"jackspeak": "^4.1.1",
|
||||
"minimatch": "^10.0.3",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^2.0.0"
|
||||
|
@ -1756,37 +1779,14 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/balanced-match": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz",
|
||||
"integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.1.tgz",
|
||||
"integrity": "sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minimatch": {
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.2.tgz",
|
||||
"integrity": "sha512-+9TJCIYXgZ2Dm5LxVCFsa8jOm+evMwXHFI0JM1XROmkfkpz8/iLLDh+TwSmyIBrs6C6Xu9294/fq8cBA+P6AqA==",
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
|
||||
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^4.0.1"
|
||||
"@isaacs/brace-expansion": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "coryd.dev",
|
||||
"version": "9.2.9",
|
||||
"version": "10.0.0",
|
||||
"description": "The source for my personal site. Built using 11ty (and other tools).",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
|
|
@ -24,7 +24,6 @@ SELECT
|
|||
'url', CONCAT(globals.url, p.permalink),
|
||||
'type', 'page'
|
||||
) AS metadata,
|
||||
p.updated,
|
||||
(
|
||||
SELECT
|
||||
json_agg(
|
||||
|
|
34
queries/views/media/music/week/summary.sql
Normal file
34
queries/views/media/music/week/summary.sql
Normal file
|
@ -0,0 +1,34 @@
|
|||
CREATE OR REPLACE VIEW optimized_week_music AS
|
||||
SELECT json_build_object(
|
||||
'week_artists', (
|
||||
SELECT json_agg(a ORDER BY a.plays DESC)
|
||||
FROM (
|
||||
SELECT * FROM week_artists ORDER BY plays DESC LIMIT 8
|
||||
) a
|
||||
),
|
||||
'week_albums', (
|
||||
SELECT json_agg(al ORDER BY al.plays DESC)
|
||||
FROM (
|
||||
SELECT * FROM week_albums ORDER BY plays DESC LIMIT 8
|
||||
) al
|
||||
),
|
||||
'week_genres', (
|
||||
SELECT json_agg(g ORDER BY g.plays DESC)
|
||||
FROM (
|
||||
SELECT * FROM week_genres ORDER BY plays DESC LIMIT 5
|
||||
) g
|
||||
),
|
||||
'recent_tracks', (
|
||||
SELECT json_agg(r ORDER BY r.listened_at DESC)
|
||||
FROM (
|
||||
SELECT * FROM recent_tracks ORDER BY listened_at DESC LIMIT 10
|
||||
) r
|
||||
),
|
||||
'week_summary', (
|
||||
SELECT json_build_object(
|
||||
'total_tracks', (SELECT COUNT(*) FROM week_tracks),
|
||||
'total_artists', (SELECT COUNT(*) FROM week_artists),
|
||||
'total_albums', (SELECT COUNT(*) FROM week_albums)
|
||||
)
|
||||
)
|
||||
) AS week_music;
|
|
@ -17,7 +17,7 @@
|
|||
<VirtualHost *:443>
|
||||
ServerAdmin hi@coryd.dev
|
||||
ServerName www.coryd.dev
|
||||
DocumentRoot /var/www/coryd.dev
|
||||
DocumentRoot /var/www/coryd.dev/public
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
function getTablerIcon($iconName, $class = '', $size = 24)
|
||||
{
|
||||
$icons = [
|
||||
'arrow-left' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" /></svg>',
|
||||
'arrow-right' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M13 18l6 -6" /><path d="M13 6l6 6" /></svg>',
|
||||
'article' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>',
|
||||
'books' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-books"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M9 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M5 8h4" /><path d="M9 16h4" /><path d="M13.803 4.56l2.184 -.53c.562 -.135 1.133 .19 1.282 .732l3.695 13.418a1.02 1.02 0 0 1 -.634 1.219l-.133 .041l-2.184 .53c-.562 .135 -1.133 -.19 -1.282 -.732l-3.695 -13.418a1.02 1.02 0 0 1 .634 -1.219l.133 -.041z" /><path d="M14 9l4 -1" /><path d="M16 16l3.923 -.98" /></svg>',
|
||||
'device-tv-old' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-tv-old"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M16 3l-4 4l-4 -4" /><path d="M15 7v13" /><path d="M18 15v.01" /><path d="M18 12v.01" /></svg>',
|
||||
'headphones' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-headphones"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M15 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M4 15v-3a8 8 0 0 1 16 0v3" /></svg>',
|
||||
'movie' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M8 4l0 16" /><path d="M16 4l0 16" /><path d="M4 8l4 0" /><path d="M4 16l4 0" /><path d="M4 12l16 0" /><path d="M16 8l4 0" /><path d="M16 16l4 0" /></svg>',
|
||||
'star' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-star"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>',
|
||||
'vinyl' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-vinyl"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 3.937a9 9 0 1 0 5 8.063" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4l-3.5 10l-2.5 2" /></svg>'
|
||||
];
|
||||
|
||||
return $icons[$iconName] ?? '<span class="icon-placeholder">[Missing: ' . htmlspecialchars($iconName) . ']</span>';
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,112 +0,0 @@
|
|||
<?php
|
||||
|
||||
function renderMediaGrid(array $items, int $count = 0, string $loading = 'lazy') {
|
||||
$limit = $count > 0 ? $count : count($items);
|
||||
$firstType = $items[0]['type'] ?? ($items[0]['grid']['type'] ?? '');
|
||||
$shapeClass = in_array($firstType, ['books', 'movies', 'tv']) ? 'vertical' : 'square';
|
||||
|
||||
echo '<div class="media-grid ' . $shapeClass . '">';
|
||||
|
||||
foreach (array_slice($items, 0, $limit) as $item) {
|
||||
$grid = $item['grid'] ?? $item;
|
||||
$alt = htmlspecialchars($grid['alt'] ?? '');
|
||||
$image = htmlspecialchars($grid['image'] ?? '');
|
||||
$title = htmlspecialchars($grid['title'] ?? '');
|
||||
$subtext = htmlspecialchars($grid['subtext'] ?? '');
|
||||
$url = $grid['url'] ?? null;
|
||||
$type = $item['type'] ?? '';
|
||||
$isVertical = in_array($type, ['books', 'movies', 'tv']);
|
||||
$imageClass = $isVertical ? 'vertical' : 'square';
|
||||
$width = $isVertical ? 120 : 150;
|
||||
$height = $isVertical ? 184 : 150;
|
||||
|
||||
$openLink = $url ? '<a class="' . htmlspecialchars($type) . '" href="' . htmlspecialchars($url) . '" title="' . $alt . '">' : '';
|
||||
$closeLink = $url ? '</a>' : '';
|
||||
|
||||
echo $openLink;
|
||||
echo '<div class="media-grid-item">';
|
||||
|
||||
if ($title || $subtext) {
|
||||
echo '<div class="meta-text media-highlight">';
|
||||
if ($title) echo '<div class="header">' . $title . '</div>';
|
||||
if ($subtext) echo '<div class="subheader">' . $subtext . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '<img
|
||||
srcset="' . $image . '?class=' . $imageClass . 'sm&type=webp ' . $width . 'w, ' .
|
||||
$image . '?class=' . $imageClass . 'md&type=webp ' . ($width * 2) . 'w"
|
||||
sizes="(max-width: 450px) ' . $width . 'px, ' . ($width * 2) . 'px"
|
||||
src="' . $image . '?class=' . $imageClass . 'sm&type=webp"
|
||||
alt="' . $alt . '"
|
||||
loading="' . $loading . '"
|
||||
decoding="async"
|
||||
width="' . $width . '"
|
||||
height="' . $height . '"
|
||||
>';
|
||||
echo '</div>';
|
||||
echo $closeLink;
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
function renderAssociatedMedia(
|
||||
array $artists = [],
|
||||
array $books = [],
|
||||
array $genres = [],
|
||||
array $movies = [],
|
||||
array $posts = [],
|
||||
array $shows = [],
|
||||
) {
|
||||
$sections = [
|
||||
"artists" => ["icon" => "headphones", "css_class" => "music", "label" => "Related artist(s)", "hasGrid" => true],
|
||||
"books" => ["icon" => "books", "css_class" => "books", "label" => "Related book(s)", "hasGrid" => true],
|
||||
"genres" => ["icon" => "headphones", "css_class" => "music", "label" => "Related genre(s)", "hasGrid" => false],
|
||||
"movies" => ["icon" => "movie", "css_class" => "movies", "label" => "Related movie(s)", "hasGrid" => true],
|
||||
"posts" => ["icon" => "article", "css_class" => "article", "label" => "Related post(s)", "hasGrid" => false],
|
||||
"shows" => ["icon" => "device-tv-old", "css_class" => "tv", "label" => "Related show(s)", "hasGrid" => true]
|
||||
];
|
||||
|
||||
$allMedia = compact('artists', 'books', 'genres', 'movies', 'posts', 'shows');
|
||||
|
||||
echo '<div class="associated-media">';
|
||||
|
||||
foreach ($sections as $key => $section) {
|
||||
$items = $allMedia[$key];
|
||||
|
||||
if (empty($items)) continue;
|
||||
|
||||
echo '<h3 id="' . htmlspecialchars($key) . '" class="' . htmlspecialchars($section['css_class']) . '">';
|
||||
echo getTablerIcon($section['icon']);
|
||||
echo htmlspecialchars($section['label']);
|
||||
echo '</h3>';
|
||||
|
||||
if ($section['hasGrid']) {
|
||||
renderMediaGrid($items);
|
||||
} else {
|
||||
echo '<ul>';
|
||||
foreach ($items as $item) {
|
||||
echo '<li class="' . htmlspecialchars($section['css_class']) . '">';
|
||||
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title'] ?? $item['name'] ?? '') . '</a>';
|
||||
|
||||
if ($key === "artists" && isset($item['total_plays']) && $item['total_plays'] > 0) {
|
||||
echo ' (' . htmlspecialchars($item['total_plays']) . ' play' . ($item['total_plays'] > 1 ? 's' : '') . ')';
|
||||
} elseif ($key === "books" && isset($item['author'])) {
|
||||
echo ' by ' . htmlspecialchars($item['author']);
|
||||
} elseif (($key === "movies" || $key === "shows") && isset($item['year'])) {
|
||||
echo ' (' . htmlspecialchars($item['year']) . ')';
|
||||
} elseif ($key === "posts" && isset($item['date'])) {
|
||||
echo ' (' . date("F j, Y", strtotime($item['date'])) . ')';
|
||||
}
|
||||
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
function setupPageMetadata(array $page, string $requestUri): array {
|
||||
$globals = $page['globals'] ?? [];
|
||||
|
||||
$title = htmlspecialchars($page["metadata"]["title"] ?? "", ENT_QUOTES, "UTF-8");
|
||||
$description = htmlspecialchars($page["metadata"]["description"] ?? "", ENT_QUOTES, "UTF-8");
|
||||
$image = htmlspecialchars(
|
||||
$page["metadata"]["open_graph_image"] ?? $globals["metadata"]["open_graph_image"] ?? "",
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$fullUrl = $globals["url"] . $requestUri;
|
||||
$oembedUrl = $globals["url"] . "/oembed" . $requestUri;
|
||||
|
||||
return [
|
||||
'pageTitle' => $title,
|
||||
'pageDescription' => $description,
|
||||
'ogImage' => $image,
|
||||
'fullUrl' => $fullUrl,
|
||||
'oembedUrl' => $oembedUrl,
|
||||
'globals' => $globals
|
||||
];
|
||||
}
|
||||
|
||||
function cleanMeta($value) {
|
||||
$value = trim($value ?? '');
|
||||
$value = str_replace(["\r", "\n"], ' ', $value);
|
||||
$value = preg_replace('/\s+/', ' ', $value);
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/icons.php';
|
||||
|
||||
function renderPaginator(array $pagination, int $totalPages): void {
|
||||
if (!$pagination || $totalPages <= 1) return;
|
||||
?>
|
||||
|
||||
<script type="module" src="/assets/scripts/components/select-pagination.js" defer></script>
|
||||
<nav aria-label="Pagination" class="pagination">
|
||||
<?php if (!empty($pagination['href']['previous'])): ?>
|
||||
<a href="<?= $pagination['href']['previous'] ?>" aria-label="Previous page">
|
||||
<?= getTablerIcon('arrow-left') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span><?= getTablerIcon('arrow-left') ?></span>
|
||||
<?php endif; ?>
|
||||
<select-pagination data-base-index="1">
|
||||
<select class="client-side" aria-label="Page selection">
|
||||
<?php foreach ($pagination['pages'] as $i): ?>
|
||||
<option value="<?= $i ?>" <?= ($pagination['pageNumber'] === $i) ? 'selected' : '' ?>>
|
||||
<?= $i ?> of <?= $totalPages ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<noscript>
|
||||
<p><span aria-current="page"><?= $pagination['pageNumber'] ?></span> of <?= $totalPages ?></p>
|
||||
</noscript>
|
||||
</select-pagination>
|
||||
<?php if (!empty($pagination['href']['next'])): ?>
|
||||
<a href="<?= $pagination['href']['next'] ?>" aria-label="Next page">
|
||||
<?= getTablerIcon('arrow-right') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span><?= getTablerIcon('arrow-right') ?></span>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
|
||||
<?php
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
function redirectTo404(): void {
|
||||
header("Location: /404/", true, 302);
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Kaoken\MarkdownIt\MarkdownIt;
|
||||
use Kaoken\MarkdownIt\Plugins\MarkdownItFootnote;
|
||||
|
||||
function truncateText($text, $limit = 50, $ellipsis = "...")
|
||||
{
|
||||
if (mb_strwidth($text, "UTF-8") <= $limit) return $text;
|
||||
|
||||
$truncated = mb_substr($text, 0, $limit, "UTF-8");
|
||||
$lastSpace = mb_strrpos($truncated, " ", 0, "UTF-8");
|
||||
|
||||
if ($lastSpace !== false) $truncated = mb_substr($truncated, 0, $lastSpace, "UTF-8");
|
||||
|
||||
return $truncated . $ellipsis;
|
||||
}
|
||||
|
||||
function parseMarkdown($markdown)
|
||||
{
|
||||
if (empty($markdown)) return '';
|
||||
|
||||
$md = new MarkdownIt([
|
||||
"html" => true,
|
||||
"linkify" => true,
|
||||
]);
|
||||
|
||||
$md->plugin(new MarkdownItFootnote());
|
||||
|
||||
return $md->render($markdown);
|
||||
}
|
||||
|
||||
function parseCountryField($countryField) {
|
||||
if (empty($countryField)) return null;
|
||||
|
||||
$delimiters = [',', '/', '&', ' and '];
|
||||
$countries = [$countryField];
|
||||
|
||||
foreach ($delimiters as $delimiter) {
|
||||
$tempCountries = [];
|
||||
|
||||
foreach ($countries as $country) {
|
||||
$tempCountries = array_merge($tempCountries, explode($delimiter, $country));
|
||||
}
|
||||
|
||||
$countries = $tempCountries;
|
||||
}
|
||||
|
||||
$countries = array_map('trim', $countries);
|
||||
$countries = array_map('getCountryName', $countries);
|
||||
$countries = array_filter($countries);
|
||||
|
||||
return implode(', ', array_unique($countries));
|
||||
}
|
||||
|
||||
function getCountryName($countryName) {
|
||||
$isoCodes = new \Sokil\IsoCodes\IsoCodesFactory();
|
||||
$countries = $isoCodes->getCountries();
|
||||
$country = $countries->getByAlpha2($countryName);
|
||||
|
||||
if ($country) return $country->getName();
|
||||
|
||||
return ucfirst(strtolower($countryName));
|
||||
}
|
||||
|
||||
function pluralize($count, $string, $trailing = '') {
|
||||
if ((int)$count === 1) return $string;
|
||||
|
||||
return $string . 's' . ($trailing ? $trailing : '');
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
function renderTags(array $tags): void {
|
||||
if (empty($tags)) return;
|
||||
|
||||
echo '<div class="tags">';
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$slug = strtolower(trim($tag));
|
||||
echo '<a href="/tags/' . rawurlencode($slug) . '">#' . htmlspecialchars($slug) . '</a>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
class NowPlaying extends HTMLElement {
|
||||
static tagName = "now-playing";
|
||||
|
||||
static register(tagName = this.tagName, registry = globalThis.customElements) {
|
||||
registry.define(tagName, this);
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
this.contentElement = this.querySelector(".content");
|
||||
if (!this.contentElement) return;
|
||||
|
||||
const cache = localStorage.getItem("now-playing-cache");
|
||||
if (cache) this.updateHTML(JSON.parse(cache));
|
||||
|
||||
await this.fetchAndUpdate();
|
||||
}
|
||||
|
||||
async fetchAndUpdate() {
|
||||
try {
|
||||
const data = await this.fetchData();
|
||||
const newHTML = data?.content;
|
||||
|
||||
if (newHTML && newHTML !== this.contentElement.innerHTML) {
|
||||
this.updateHTML(newHTML);
|
||||
localStorage.setItem("now-playing-cache", JSON.stringify(newHTML));
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
updateHTML(value) {
|
||||
this.contentElement.innerHTML = value;
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
return fetch(`/api/playing.php?nocache=${Date.now()}`)
|
||||
.then(response => response.json())
|
||||
.catch(() => ({}));
|
||||
}
|
||||
}
|
||||
|
||||
NowPlaying.register();
|
|
@ -7,7 +7,6 @@ const staticAssets = [
|
|||
'/assets/fonts/dmi.woff2',
|
||||
'/assets/fonts/ml.woff2',
|
||||
'/assets/scripts/index.js',
|
||||
'/assets/scripts/components/now-playing.js',
|
||||
'/assets/scripts/components/select-pagination.js',
|
||||
];
|
||||
|
||||
|
|
|
@ -501,10 +501,4 @@ footer {
|
|||
|
||||
footer {
|
||||
margin: var(--sizing-3xl) auto 0;
|
||||
|
||||
.updated {
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchLatestListen = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_latest_listen?select=*`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const trackData = data[0];
|
||||
if (!trackData) {
|
||||
return { content: "🎧 No recent listens found" };
|
||||
}
|
||||
|
||||
const emoji = trackData.artist_emoji || trackData.genre_emoji || "🎧";
|
||||
|
||||
return {
|
||||
content: `${emoji} ${
|
||||
trackData.track_name
|
||||
} by <a href="https://www.coryd.dev${trackData.url}">${trackData.artist_name}</a>`,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching the latest listen:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchLatestListen();
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchRecentMedia = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_recent_media?select=*`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const [{ recent_activity } = {}] = data;
|
||||
|
||||
return recent_activity || [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching recent media data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchRecentMedia();
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<script type="module" src="/assets/scripts/components/now-playing.js?v={% appVersion %}"></script>
|
||||
<p class="{{ section }}">
|
||||
<mark>Now playing</mark> 
|
||||
<now-playing>
|
||||
<span class="content">{{ nowPlaying }}</span>
|
||||
</now-playing>
|
||||
</p>
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
|
||||
use App\Classes\ArtistFetcher;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
|
||||
use App\Classes\BookFetcher;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
|
||||
use App\Classes\GenreFetcher;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
|
||||
use App\Classes\MovieFetcher;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
|
||||
use App\Classes\ShowFetcher;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require __DIR__ . "/../server/utils/init.php";
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../config/dynamic/init.php";
|
||||
|
||||
use App\Classes\TagFetcher;
|
||||
use App\Classes\GlobalsFetcher;
|
15
src/includes/dynamic/media/now-playing.php.liquid
Normal file
15
src/includes/dynamic/media/now-playing.php.liquid
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
use App\Classes\LatestListenHandler;
|
||||
|
||||
$handler = new LatestListenHandler();
|
||||
$data = $handler->getLatestListen();
|
||||
|
||||
if (!empty($data['content'])):
|
||||
|
||||
?>
|
||||
<p class="now-playing">
|
||||
<mark>Now playing</mark> 
|
||||
<span class="content"><?= $data['content'] ?></span>
|
||||
</p>
|
||||
<?php endif; ?>
|
15
src/includes/dynamic/media/recent-media.php.liquid
Normal file
15
src/includes/dynamic/media/recent-media.php.liquid
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
use App\Classes\RecentMediaHandler;
|
||||
|
||||
$handler = new RecentMediaHandler();
|
||||
$media = $handler->getRecentMedia();
|
||||
|
||||
if (!empty($media['recentMusic'])) echo renderMediaGrid($media['recentMusic'], 0, 'eager');
|
||||
if (!empty($media['recentWatchedRead'])) echo renderMediaGrid($media['recentWatchedRead'], 0, 'eager');
|
||||
|
||||
?>
|
||||
{% render "static/blocks/banners/rss.liquid",
|
||||
url:"/feeds",
|
||||
text:"Subscribe to my posts, movies, books, links or activity feed(s)"
|
||||
%}
|
41
src/includes/dynamic/media/recent-tracks.php.liquid
Normal file
41
src/includes/dynamic/media/recent-tracks.php.liquid
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php if (!empty($music['recent_tracks'])): ?>
|
||||
<div class="music-chart">
|
||||
<?php foreach (array_slice($music['recent_tracks'], 0, 10) as $item): ?>
|
||||
<?php $chart = $item['chart'] ?? []; ?>
|
||||
<div class="chart-item">
|
||||
<div class="meta">
|
||||
<a href="<?= htmlspecialchars($chart['url'] ?? '#') ?>">
|
||||
<img
|
||||
srcset="
|
||||
<?= htmlspecialchars($chart['image']) ?>?class=w50&type=webp 50w,
|
||||
<?= htmlspecialchars($chart['image']) ?>?class=w100&type=webp 100w
|
||||
"
|
||||
sizes="(max-width: 450px) 50px, 100px"
|
||||
src="<?= htmlspecialchars($chart['image']) ?>?class=w50&type=webp"
|
||||
alt="<?= htmlspecialchars($chart['alt'] ?? '') ?>"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
</a>
|
||||
<div class="meta-text">
|
||||
<a class="title" href="<?= htmlspecialchars($chart['url'] ?? '#') ?>">
|
||||
<?= htmlspecialchars($chart['title'] ?? '') ?>
|
||||
</a>
|
||||
<span class="subheader"><?= htmlspecialchars($chart['subtext'] ?? '') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($chart['played_at'])): ?>
|
||||
<?php
|
||||
$timestamp = (int) $chart['played_at'];
|
||||
$playedAt = (new DateTime("@$timestamp"))->setTimezone(new DateTimeZone('America/Los_Angeles'));
|
||||
?>
|
||||
<time datetime="<?= $playedAt->format('c') ?>">
|
||||
<?= $playedAt->format('F j, g:ia') ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
|
@ -1,15 +0,0 @@
|
|||
{% render "media/grid.liquid",
|
||||
globals:globals,
|
||||
data:media.recentMusic,
|
||||
loading:"eager"
|
||||
%}
|
||||
{% render "media/grid.liquid",
|
||||
globals:globals,
|
||||
data:media.recentWatchedRead,
|
||||
shape:"vertical",
|
||||
loading:"eager"
|
||||
%}
|
||||
{% render "blocks/banners/rss.liquid",
|
||||
url:"/feeds",
|
||||
text:"Subscribe to my posts, movies, books, links or activity feed(s)"
|
||||
%}
|
|
@ -1,22 +0,0 @@
|
|||
{%- assign updateTime = "" -%}
|
||||
{%- if updated == "now" -%}
|
||||
{%- assign updateTime = "now" | date:"%B %-d, %l:%M %P", "America/Los_Angeles" -%}
|
||||
{%- elsif pageUpdated -%}
|
||||
{%- assign updateTime = page.updated | date:"%B %-d, %l:%M %P", "America/Los_Angeles" -%}
|
||||
{%- endif -%}
|
||||
<footer>
|
||||
{%- if updateTime -%}
|
||||
<p class="updated"><em>This page was last updated on {{ updateTime | strip }}.</em></p>
|
||||
{%- endif -%}
|
||||
{% render "nav/menu.liquid",
|
||||
page:page,
|
||||
nav:nav.footer_icons
|
||||
class:"social"
|
||||
%}
|
||||
{% render "nav/menu.liquid",
|
||||
page:page,
|
||||
nav:nav.footer_text
|
||||
class:"sub-pages"
|
||||
separator:true
|
||||
%}
|
||||
</footer>
|
|
@ -1,30 +0,0 @@
|
|||
<div class="music-chart">
|
||||
{%- for item in data limit:10 -%}
|
||||
<div class="chart-item">
|
||||
<div class="meta">
|
||||
<a href="{{ item.chart.url }}">
|
||||
<img
|
||||
srcset="
|
||||
{{ item.chart.image }}?class=w50&type=webp 50w,
|
||||
{{ item.chart.image }}?class=w100&type=webp 100w
|
||||
"
|
||||
sizes="(max-width: 450px) 50px, 100px"
|
||||
src="{{ item.chart.image }}?class=w50&type=webp"
|
||||
alt="{{ item.chart.alt | replaceQuotes }}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
</a>
|
||||
<div class="meta-text">
|
||||
<a class="title" href="{{ item.chart.url }}">{{ item.chart.title }}</a>
|
||||
<span class="subheader">{{ item.chart.subtext }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<time datetime="{{ item.chart.played_at | date: "%Y-%m-%dT%H:%M:%S%:z", "America/Los_Angeles" }}">
|
||||
{{ item.chart.played_at | date:"%B %-d, %-I:%M%p", "America/Los_Angeles" }}
|
||||
</time>
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</div>
|
|
@ -35,7 +35,7 @@
|
|||
{% if key == "books" or key == "movies" or key == "shows" %}
|
||||
{% assign shape = "vertical" %}
|
||||
{% endif %}
|
||||
{% render "media/grid.liquid",
|
||||
{% render "static/media/grid.liquid",
|
||||
data:items,
|
||||
shape:shape
|
||||
%}
|
|
@ -1,22 +1,22 @@
|
|||
{%- for block in blocks -%}
|
||||
{%- case block.type -%}
|
||||
{%- when "calendar_banner" -%}
|
||||
{% render "blocks/banners/calendar.liquid",
|
||||
{% render "static/blocks/banners/calendar.liquid",
|
||||
url:block.url,
|
||||
text:block.text
|
||||
%}
|
||||
{%- when "divider" -%}
|
||||
{{ block.markup | markdown }}
|
||||
{%- when "forgejo_banner" -%}
|
||||
{% render "blocks/banners/forgejo.liquid",
|
||||
{% render "static/blocks/banners/forgejo.liquid",
|
||||
url:block.url
|
||||
%}
|
||||
{%- when "github_banner" -%}
|
||||
{% render "blocks/banners/github.liquid",
|
||||
{% render "static/blocks/banners/github.liquid",
|
||||
url:block.url
|
||||
%}
|
||||
{%- when "hero" -%}
|
||||
{% render "blocks/hero.liquid",
|
||||
{% render "static/blocks/hero.liquid",
|
||||
globals:globals,
|
||||
image:block.image,
|
||||
alt:block.alt
|
||||
|
@ -24,17 +24,17 @@
|
|||
{%- when "markdown" -%}
|
||||
{{ block.text | markdown }}
|
||||
{%- when "npm_banner" -%}
|
||||
{% render "blocks/banners/npm.liquid",
|
||||
{% render "static/blocks/banners/npm.liquid",
|
||||
url:block.url,
|
||||
command:block.command
|
||||
%}
|
||||
{%- when "rss_banner" -%}
|
||||
{% render "blocks/banners/rss.liquid",
|
||||
{% render "static/blocks/banners/rss.liquid",
|
||||
url:block.url,
|
||||
text:block.text
|
||||
%}
|
||||
{%- when "youtube_player" -%}
|
||||
{% render "blocks/youtube-player.liquid",
|
||||
{% render "static/blocks/youtube-player.liquid",
|
||||
url:block.url
|
||||
%}
|
||||
{%- endcase -%}
|
|
@ -1,7 +1,6 @@
|
|||
<article class="intro">
|
||||
{{ intro }}
|
||||
{% render "blocks/now-playing.liquid",
|
||||
nowPlaying:nowPlaying
|
||||
{% render "dynamic/media/now-playing.php.liquid",
|
||||
section:"music"
|
||||
%}
|
||||
<hr />
|
|
@ -10,7 +10,7 @@
|
|||
• {{ item.label }}
|
||||
{%- if item.notes -%}
|
||||
{% assign notes = item.notes | markdown %}
|
||||
{% render "blocks/dialog.liquid",
|
||||
{% render "static/blocks/dialog.liquid",
|
||||
icon:"info-circle",
|
||||
label:"View info about this concert"
|
||||
dynamic:"optimized_concerts",
|
||||
|
@ -49,7 +49,7 @@
|
|||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
</h3>
|
||||
{% render "blocks/tags.liquid",
|
||||
{% render "static/blocks/tags.liquid",
|
||||
tags:item.tags
|
||||
%}
|
||||
</article>
|
13
src/includes/static/layout/footer.liquid
Normal file
13
src/includes/static/layout/footer.liquid
Normal file
|
@ -0,0 +1,13 @@
|
|||
<footer>
|
||||
{% render "static/nav/menu.liquid",
|
||||
page:page,
|
||||
nav:nav.footer_icons
|
||||
class:"social"
|
||||
%}
|
||||
{% render "static/nav/menu.liquid",
|
||||
page:page,
|
||||
nav:nav.footer_text
|
||||
class:"sub-pages"
|
||||
separator:true
|
||||
%}
|
||||
</footer>
|
|
@ -20,13 +20,13 @@
|
|||
<a href="/" tabindex="0">{{ headerContent }}</a>
|
||||
{%- endif -%}
|
||||
</h1>
|
||||
{% render "nav/menu.liquid",
|
||||
{% render "static/nav/menu.liquid",
|
||||
page:page,
|
||||
nav:nav.primary_icons
|
||||
class:"icons"
|
||||
%}
|
||||
</section>
|
||||
{% render "nav/menu.liquid",
|
||||
{% render "static/nav/menu.liquid",
|
||||
page:page,
|
||||
nav:nav.primary
|
||||
class:"primary"
|
|
@ -42,6 +42,6 @@
|
|||
</a>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
{% render "nav/paginator.liquid",
|
||||
{% render "static/nav/paginator.liquid",
|
||||
pagination:pagination
|
||||
%}
|
|
@ -6,7 +6,7 @@
|
|||
<span class="subheader">{{ item.chart.plays }} {{ playsLabel }}</span>
|
||||
</div>
|
||||
<div class="chart-item-progress">
|
||||
{% render "media/progress-bar.liquid",
|
||||
{% render "static/media/progress-bar.liquid",
|
||||
percentage:item.chart.percentage
|
||||
%}
|
||||
</div>
|
|
@ -3,7 +3,7 @@
|
|||
{%- if count -%}
|
||||
{%- for item in data limit:count -%}
|
||||
<li value="{{ item.chart.rank }}">
|
||||
{% render "media/music/charts/item.liquid",
|
||||
{% render "static/media/music/charts/item.liquid",
|
||||
item:item
|
||||
%}
|
||||
</li>
|
||||
|
@ -11,7 +11,7 @@
|
|||
{%- else -%}
|
||||
{%- for item in pagination.items -%}
|
||||
<li value="{{ item.chart.rank }}">
|
||||
{% render "media/music/charts/item.liquid",
|
||||
{% render "static/media/music/charts/item.liquid",
|
||||
item:item
|
||||
%}
|
||||
</li>
|
||||
|
@ -19,6 +19,6 @@
|
|||
{%- endif -%}
|
||||
</ol>
|
||||
</div>
|
||||
{% render "nav/paginator.liquid",
|
||||
{% render "static/nav/paginator.liquid",
|
||||
pagination:pagination
|
||||
%}
|
|
@ -9,7 +9,7 @@
|
|||
({{ movie.year }})
|
||||
</div>
|
||||
</div>
|
||||
{% render "blocks/hero.liquid",
|
||||
{% render "static/blocks/hero.liquid",
|
||||
globals:globals,
|
||||
image:movie.backdrop,
|
||||
alt:movie.title
|
|
@ -2,7 +2,7 @@
|
|||
{%- assign source = page -%}
|
||||
{%- case schema -%}
|
||||
{%- when 'artist', 'genre', 'book', 'movie', 'show', 'tags' -%}
|
||||
{% render "fetchers/{{ schema }}.php.liquid" %}
|
||||
{% render "dynamic/fetchers/{{ schema }}.php.liquid" %}
|
||||
{%- when 'blog' -%}
|
||||
{%- assign source = post -%}
|
||||
{%- when 'music-index', 'music-week-artists' -%}
|
||||
|
@ -37,7 +37,7 @@
|
|||
{%- assign fullUrl = meta.url -%}
|
||||
{%- assign oembedUrl = globals.url | append: "/oembed" | append: page.url -%}
|
||||
{%- if type == 'dynamic' -%}
|
||||
{% render "metadata/dynamic.php.liquid"
|
||||
{% render "dynamic/metadata/index.php.liquid"
|
||||
fullUrl: fullUrl,
|
||||
oembedUrl: oembedUrl,
|
||||
pageTitle: meta.title,
|
||||
|
@ -46,7 +46,7 @@
|
|||
globals: globals,
|
||||
%}
|
||||
{%- else -%}
|
||||
{% render "metadata/static.liquid"
|
||||
{% render "static/metadata/static.liquid"
|
||||
fullUrl: fullUrl,
|
||||
oembedUrl: oembedUrl,
|
||||
pageTitle: meta.title,
|
||||
|
@ -55,7 +55,7 @@
|
|||
globals: globals,
|
||||
%}
|
||||
{%- endif %}
|
||||
{% render "metadata/base.liquid"
|
||||
{% render "static/metadata/base.liquid"
|
||||
pageTitle: meta.title,
|
||||
globals: globals,
|
||||
eleventy: eleventy,
|
|
@ -1,6 +1,7 @@
|
|||
{%- assign categoryUrl = link.permalink | downcase -%}
|
||||
{%- assign isHttp = categoryUrl contains "http" -%}
|
||||
{%- if categoryUrl | isLinkActive:page.url -%}
|
||||
{%- assign url = page.activeUrl | default: page.url -%}
|
||||
{%- if categoryUrl | isLinkActive:url -%}
|
||||
{%- capture linkClass -%}
|
||||
{%- if link.section -%}button{%- endif -%}
|
||||
{%- if link.icon -%}icon{%- endif -%}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue