v3
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* index_config/bludit.php
|
||||
* ─────────────────────────────────────────────────────────────────────────────
|
||||
* Fetches the latest posts from a Bludit blog via its REST API.
|
||||
* Returns an array of post arrays, or an empty array on failure.
|
||||
*
|
||||
* Requires BLUDIT_URL, BLUDIT_API_TOKEN, BLUDIT_POST_COUNT,
|
||||
* BLUDIT_SNIPPET_LEN, BLUDIT_CACHE_TTL (all set in config.php).
|
||||
*
|
||||
* Usage (from index.php):
|
||||
* $blog_posts = bludit_get_posts();
|
||||
*/
|
||||
|
||||
function bludit_get_posts(): array {
|
||||
// Bail early if not configured
|
||||
if (!defined('BLUDIT_API_TOKEN') || BLUDIT_API_TOKEN === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cache_file = sys_get_temp_dir() . '/tc_bludit_cache_' . md5(BLUDIT_URL) . '.json';
|
||||
$ttl = (int)(BLUDIT_CACHE_TTL ?? 300);
|
||||
|
||||
// Serve from cache if fresh
|
||||
if ($ttl > 0 && file_exists($cache_file) && (time() - filemtime($cache_file)) < $ttl) {
|
||||
$cached = json_decode(file_get_contents($cache_file), true);
|
||||
if (is_array($cached)) return $cached;
|
||||
}
|
||||
|
||||
// Build API request
|
||||
$api_url = rtrim(BLUDIT_URL, '/') . '/api/pages';
|
||||
$count = max(1, min(20, (int)(BLUDIT_POST_COUNT ?? 5)));
|
||||
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => implode("\r\n", [
|
||||
'X-Bludit-API-Token: ' . BLUDIT_API_TOKEN,
|
||||
'Accept: application/json',
|
||||
]),
|
||||
'timeout' => 5,
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
'ssl' => ['verify_peer' => true],
|
||||
]);
|
||||
|
||||
$raw = @file_get_contents($api_url, false, $ctx);
|
||||
if ($raw === false) return [];
|
||||
|
||||
$data = json_decode($raw, true);
|
||||
if (!isset($data['data']) || !is_array($data['data'])) return [];
|
||||
|
||||
$snippet_len = (int)(BLUDIT_SNIPPET_LEN ?? 110);
|
||||
$posts = [];
|
||||
|
||||
foreach (array_slice($data['data'], 0, $count) as $page) {
|
||||
// Skip drafts / non-published
|
||||
if (($page['type'] ?? '') !== 'published') continue;
|
||||
|
||||
// Build snippet from content (strip HTML, collapse whitespace)
|
||||
$raw_body = $page['content'] ?? $page['contentRaw'] ?? '';
|
||||
$plain = preg_replace('/\s+/', ' ', strip_tags(html_entity_decode($raw_body)));
|
||||
$snippet = mb_strlen($plain) > $snippet_len
|
||||
? mb_substr($plain, 0, $snippet_len) . '…'
|
||||
: $plain;
|
||||
|
||||
$posts[] = [
|
||||
'title' => html_entity_decode($page['title'] ?? 'Untitled'),
|
||||
'snippet' => $snippet,
|
||||
'url' => $page['permalink'] ?? (rtrim(BLUDIT_URL, '/') . '/' . ($page['key'] ?? '')),
|
||||
'date' => $page['date'] ?? '',
|
||||
'tags' => array_values((array)($page['tags'] ?? [])),
|
||||
];
|
||||
|
||||
if (count($posts) >= $count) break;
|
||||
}
|
||||
|
||||
// Cache result
|
||||
if ($ttl > 0 && !empty($posts)) {
|
||||
file_put_contents($cache_file, json_encode($posts), LOCK_EX);
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* index_config/config.php
|
||||
* ─────────────────────────────────────────────────────────────────────────────
|
||||
* All site-wide configuration lives here.
|
||||
* Edit this file only — nothing else needs touching for routine updates.
|
||||
*/
|
||||
|
||||
// ── Site identity ─────────────────────────────────────────────────────────────
|
||||
define('SITE_NAME', 'Ty Clifford');
|
||||
define('SITE_TAGLINE', 'Hobbies are my hobby.');
|
||||
define('SITE_URL', 'https://tyclifford.com');
|
||||
define('SITE_DESCRIPTION', 'The web space of Ty Clifford');
|
||||
define('SITE_AUTHOR', 'Ty Clifford');
|
||||
define('SITE_KEYWORDS', 'Ty Clifford, Keyser, West Virginia, United States, Nerd, TikTok, Freedom of Expression');
|
||||
define('SITE_OG_IMAGE', SITE_URL . '/images/fa2.jpg');
|
||||
define('SITE_OG_ALT', 'Profile image of Ty Clifford. Making a funny face squinting.');
|
||||
define('SITE_TWITTER', '@_tyclifford');
|
||||
|
||||
// ── Profile ───────────────────────────────────────────────────────────────────
|
||||
define('PROFILE_IMG', SITE_URL . '/images/fa2.jpg');
|
||||
define('PROFILE_IMG_ALT', 'Ty Clifford — making a funny face, squinting. Eye everything.');
|
||||
|
||||
// ── Background video ──────────────────────────────────────────────────────────
|
||||
// Set BG_VIDEO_MP4 to '' to disable the video (falls back to static gradient).
|
||||
define('BG_VIDEO_MP4', SITE_URL . '/bg/BGV2.mp4');
|
||||
define('BG_VIDEO_POSTER', SITE_URL . '/images/fa2.jpg');
|
||||
// Overlay opacity over the video: 0 = none 1 = fully black
|
||||
define('BG_OVERLAY', '0.78');
|
||||
|
||||
// ── Typed cycling strings (after "living in") ─────────────────────────────────
|
||||
// Format: ['text', 'optional_css_class']
|
||||
// CSS classes available: '' | 'grad-green' | 'grad-purple'
|
||||
define('TYPED_STRINGS', serialize([
|
||||
['West Virginia', ''],
|
||||
['Keyser.', ''],
|
||||
['curiosity.', ''],
|
||||
['wonder.', 'grad-green'],
|
||||
['the multiverse.', ''],
|
||||
['your mind now.', 'grad-purple'],
|
||||
]));
|
||||
|
||||
// ── Horizontal nav pills ──────────────────────────────────────────────────────
|
||||
// Format: ['Label', 'url', 'color_class', 'emoji or ""']
|
||||
// Color classes: col-blue | col-green | col-orange | col-pink | col-peach
|
||||
// col-red | col-hotpink | col-sky
|
||||
define('NAV_LINKS', serialize([
|
||||
['Live', 'https://tyclifford.com/live/', 'col-blue', ''],
|
||||
//['Shares', 'https://tyclifford.com/trade/', 'col-green', ''],
|
||||
//['World', 'https://tyclifford.com/world/', 'col-orange', ''],
|
||||
['Patreon', 'https://go.tyclifford.com/patreon', 'col-pink', ''],
|
||||
['Blog', 'https://blog.tyclifford.com', 'col-peach', ''],
|
||||
['YouTube', 'https://go.tyclifford.com/youtube', 'col-red', ''],
|
||||
['TikTok', 'https://go.tyclifford.com/tiktok', 'col-hotpink', ''],
|
||||
['Bluesky', 'https://go.tyclifford.com/bluesky', 'col-sky', ''],
|
||||
// ['Tip Jar', '/tip/', 'col-green', ''],
|
||||
]));
|
||||
|
||||
// ── Bludit blog integration ───────────────────────────────────────────────────
|
||||
// Base URL of your Bludit install (no trailing slash).
|
||||
define('BLUDIT_URL', 'https://blog.tyclifford.com');
|
||||
// Bludit API token — find it in Admin › Settings › API.
|
||||
// Set to '' to disable the blog widget entirely.
|
||||
define('BLUDIT_API_TOKEN', '');
|
||||
// Number of posts to display (max 5 recommended).
|
||||
define('BLUDIT_POST_COUNT', 5);
|
||||
// Snippet length in characters (post body is stripped of HTML).
|
||||
define('BLUDIT_SNIPPET_LEN', 110);
|
||||
// Cache TTL in seconds for the API response (0 = no cache).
|
||||
define('BLUDIT_CACHE_TTL', 300);
|
||||
|
||||
// ── Updates feed ─────────────────────────────────────────────────────────────
|
||||
// Path to the JSON file that drives the "Updates" widget (absolute path).
|
||||
define('UPDATES_JSON', __DIR__ . '/updates.json');
|
||||
|
||||
// ── Updates auto-refresh ──────────────────────────────────────────────────────
|
||||
// How often (in seconds) the browser polls for new updates.
|
||||
// Minimum enforced by JS: 5 seconds. Recommended: 30–120.
|
||||
define('UPDATES_REFRESH_INTERVAL', 30);
|
||||
|
||||
// URL to an audio file played when new updates are detected.
|
||||
// Supports any browser-compatible format: .mp3, .ogg, .wav
|
||||
// Set to '' to disable the sound notification entirely.
|
||||
define('UPDATES_REFRESH_SOUND', SITE_URL . '');
|
||||
|
||||
// How many updates to display per page in the widget.
|
||||
// Recommended: 10–15. Visitors can page through older posts.
|
||||
define('UPDATES_PER_PAGE', 10);
|
||||
|
||||
// URL to the lightweight JSON feed endpoint used by the auto-refresher.
|
||||
// Default: the updates_feed.php file inside index_config/ (no need to change).
|
||||
define('UPDATES_FEED_URL', '/index_config/updates_feed.php');
|
||||
|
||||
// ── Donation buttons ─────────────────────────────────────────────────────────
|
||||
// Format: ['Label', 'url', 'style_key']
|
||||
// style_key: 'patreon' | 'liberapay' | 'paypal' | 'generic'
|
||||
define('DONATE_LINKS', serialize([
|
||||
['Become a Patron', 'https://go.tyclifford.com/patreon', 'patreon'],
|
||||
// ['Donate via Liberapay', 'https://go.tyclifford.com/liberapay', 'liberapay'],
|
||||
['Tip via PayPal', 'https://www.paypal.com/donate/?hosted_button_id=ZDJ9TXPPMNZ6U', 'paypal'],
|
||||
]));
|
||||
|
||||
// ── Social / contact links ────────────────────────────────────────────────────
|
||||
// Format: ['Label', 'url', 'icon_key']
|
||||
// icon_key: 'github' | 'email' | 'patreon' | 'bluesky' | 'tiktok' | 'youtube' | 'web'
|
||||
define('SOCIAL_LINKS', serialize([
|
||||
['GitHub', 'https://github.com/snick512', 'github'],
|
||||
['Bluesky', 'https://go.tyclifford.com/bluesky', 'bluesky'],
|
||||
['TikTok', 'https://go.tyclifford.com/tiktok', 'tiktok'],
|
||||
['YouTube', 'https://tyclifford.com/yt', 'youtube'],
|
||||
['ty@tyclifford.com', 'mailto:ty@tyclifford.com', 'email'],
|
||||
]));
|
||||
|
||||
// ── CTA buttons ───────────────────────────────────────────────────────────────
|
||||
// Set to '' to hide the respective button.
|
||||
define('CALENDLY_URL', 'https://calendly.com/tyclifford/list');
|
||||
define('SCHEDULE_URL', '/schedule/');
|
||||
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"id": "001",
|
||||
"text": "Deployed a new homepage design 🙈",
|
||||
"date": "2025-05-13T20:00:00Z",
|
||||
"tags": ["site", "design"],
|
||||
"link": "https://tyclifford.com/",
|
||||
"link_label": "Homepage"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* index_config/updates_feed.php
|
||||
* ─────────────────────────────────────────────────────────────────────────────
|
||||
* Lightweight JSON endpoint consumed exclusively by the JS auto-refresh in
|
||||
* index.php. Returns the full updates array (JS handles pagination client-side
|
||||
* so every poll can detect new posts regardless of which page is visible).
|
||||
*
|
||||
* Usage: GET index_config/updates_feed.php
|
||||
* GET index_config/updates_feed.php?since=<ISO-timestamp>
|
||||
*
|
||||
* The optional ?since= parameter lets the client ask "give me only posts newer
|
||||
* than this timestamp" so it can detect new arrivals without comparing full
|
||||
* arrays.
|
||||
*
|
||||
* Response:
|
||||
* {
|
||||
* "ok": true,
|
||||
* "total": <int>, // total posts in the feed
|
||||
* "new_since": <int>, // posts newer than ?since= (0 if not supplied)
|
||||
* "items": [ … ] // full array, newest first
|
||||
* }
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
// ── Load feed ─────────────────────────────────────────────────────────────────
|
||||
$posts = [];
|
||||
if (file_exists(UPDATES_JSON)) {
|
||||
$raw = json_decode(file_get_contents(UPDATES_JSON), true);
|
||||
if (is_array($raw)) $posts = $raw;
|
||||
}
|
||||
|
||||
// ── Count posts newer than ?since= ───────────────────────────────────────────
|
||||
$since = trim($_GET['since'] ?? '');
|
||||
$new_count = 0;
|
||||
|
||||
if ($since !== '') {
|
||||
try {
|
||||
$since_dt = new DateTime($since, new DateTimeZone('UTC'));
|
||||
foreach ($posts as $p) {
|
||||
try {
|
||||
$post_dt = new DateTime($p['date'] ?? '', new DateTimeZone('UTC'));
|
||||
if ($post_dt > $since_dt) $new_count++;
|
||||
} catch (Exception $e) { /* skip malformed dates */ }
|
||||
}
|
||||
} catch (Exception $e) { /* ignore bad since param */ }
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'total' => count($posts),
|
||||
'new_since' => $new_count,
|
||||
'items' => $posts,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
Reference in New Issue
Block a user