diff options
| author | Thomas Grothe <grothe.tr@gmail.com> | 2026-04-12 01:26:43 -0400 |
|---|---|---|
| committer | Thomas Grothe <grothe.tr@gmail.com> | 2026-04-12 01:26:43 -0400 |
| commit | df0f054366a81d02b28a5e2ae0d571cf5b153256 (patch) | |
| tree | 47c674a6c5809a858fefa917c68b4b8822491944 | |
| parent | d4f97aa956be051dd5b9a184557106dc7de112ac (diff) | |
updates, including removing axios, making a presence page, fixed 4chan search, vite asset bundling
| -rw-r--r-- | app/Http/Controllers/FileController.php | 6 | ||||
| -rw-r--r-- | app/Http/Controllers/SiteController.php | 15 | ||||
| -rwxr-xr-x | app/Providers/AppServiceProvider.php | 11 | ||||
| -rwxr-xr-x | package.json | 2 | ||||
| -rw-r--r-- | public/4chan_query | bin | 0 -> 9247037 bytes | |||
| -rw-r--r-- | public/js/app.js | 7 | ||||
| -rw-r--r-- | public/js/writing_create.js | 70 | ||||
| -rw-r--r-- | resources/css/app.css (renamed from public/css/app.css) | 0 | ||||
| -rw-r--r-- | resources/css/home.css (renamed from public/css/home.css) | 0 | ||||
| -rw-r--r-- | resources/css/jstoys.css (renamed from public/css/jstoys.css) | 0 | ||||
| -rw-r--r-- | resources/css/normalize.css (renamed from public/css/normalize.css) | 0 | ||||
| -rw-r--r-- | resources/css/presence.css (renamed from public/css/presence.css) | 2 | ||||
| -rw-r--r-- | resources/css/reset.css (renamed from public/css/reset.css) | 0 | ||||
| -rw-r--r-- | resources/css/skeleton.css (renamed from public/css/skeleton.css) | 0 | ||||
| -rw-r--r-- | resources/css/style.css (renamed from public/css/style.css) | 0 | ||||
| -rw-r--r-- | resources/css/writings.css (renamed from public/css/writings.css) | 0 | ||||
| -rw-r--r-- | resources/css/youtube.css | 198 | ||||
| -rw-r--r-- | resources/js/app.js | 9 | ||||
| -rw-r--r-- | resources/js/blood.js (renamed from public/js/blood.js) | 0 | ||||
| -rw-r--r-- | resources/js/blood_gpu.js (renamed from public/js/blood_gpu.js) | 0 | ||||
| -rw-r--r-- | resources/js/bootstrap.js (renamed from public/js/bootstrap.js) | 0 | ||||
| -rw-r--r-- | resources/js/echo.js (renamed from public/js/echo.js) | 0 | ||||
| -rw-r--r-- | resources/js/hn.js (renamed from public/js/hn.js) | 0 | ||||
| -rw-r--r-- | resources/js/home.js (renamed from public/js/home.js) | 0 | ||||
| -rw-r--r-- | resources/js/lib.js (renamed from public/js/lib.js) | 0 | ||||
| -rw-r--r-- | resources/js/main.js (renamed from public/js/main.js) | 25 | ||||
| -rw-r--r-- | resources/js/marked.js (renamed from public/js/marked.js) | 0 | ||||
| -rw-r--r-- | resources/js/socket.io.esm.min.js (renamed from public/js/socket.io.esm.min.js) | 0 | ||||
| -rw-r--r-- | resources/js/webgpu.js (renamed from public/js/webgpu.js) | 0 | ||||
| -rw-r--r-- | resources/js/webgpu_gltf.js (renamed from public/js/webgpu_gltf.js) | 0 | ||||
| -rw-r--r-- | resources/js/writing_create.js | 17 | ||||
| -rw-r--r-- | resources/js/writing_index.js (renamed from public/js/writing_index.js) | 0 | ||||
| -rw-r--r-- | resources/js/writing_show.js (renamed from public/js/writing_show.js) | 0 | ||||
| -rw-r--r-- | resources/js/youtube.js | 276 | ||||
| -rw-r--r-- | resources/js/youtube.js.bak | 227 | ||||
| -rw-r--r-- | resources/views/.off/graphics.html (renamed from resources/views/graphics.html) | 0 | ||||
| -rw-r--r-- | resources/views/.off/marked.blade.php (renamed from resources/views/marked.blade.php) | 4 | ||||
| -rwxr-xr-x | resources/views/4.blade.php | 5 | ||||
| -rwxr-xr-x | resources/views/home.blade.php | 5 | ||||
| -rw-r--r-- | resources/views/presence/index.blade.php | 2 | ||||
| -rw-r--r-- | resources/views/presence/show.blade.php | 1 | ||||
| -rwxr-xr-x | resources/views/template.blade.php | 3 | ||||
| -rw-r--r-- | resources/views/writings/create.blade.php | 6 | ||||
| -rw-r--r-- | resources/views/writings/edit.blade.php | 5 | ||||
| -rw-r--r-- | resources/views/writings/index.blade.php | 2 | ||||
| -rw-r--r-- | resources/views/writings/show.blade.php | 4 | ||||
| -rwxr-xr-x | routes/web.php | 8 | ||||
| -rwxr-xr-x | vite.config.js | 13 |
48 files changed, 791 insertions, 132 deletions
diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index 70d07ef..362abf6 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -26,7 +26,11 @@ class FileController extends Controller $currentDir = "${baseDir}/${dir}"; } - // Get file listings + //check if dir exists + if (!is_dir($currentDir)) { + return 'Directory not found.'; + } + $contents = scandir($currentDir); $fileData = []; diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php index 33d7dc3..301e101 100644 --- a/app/Http/Controllers/SiteController.php +++ b/app/Http/Controllers/SiteController.php @@ -105,13 +105,18 @@ class SiteController extends Controller } public function search4chan(Request $req){ - $query = $req->input('query'); Log::info('search4chan()'); - - $cmd = 'python ./4chansearch.py ' . escapeshellarg($query); - if ($req->input('board') != null) { - $cmd .= ' -b ' . escapeshellarg($req->input('board')); + $validated = $req->validate([ + 'query' => 'required|string', + 'board' => 'nullable|string' + ]); + + $cmd = './4chan_query '; + Log::info($cmd); + if (isset($validated['board'])) { + $cmd .= ' -b ' . escapeshellarg($validated['board']); } + $cmd .= ' ' . escapeshellarg($validated['query']); exec($cmd, $res, $ret); return $res; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8eee884..9d78efd 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; +use Illuminate\Support\Facades\Blade; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Http\Request; @@ -22,6 +23,16 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { + Blade::directive('v', function ($expression) { + $expr = trim($expression); + if (str_starts_with($expr, '[')) { + // Array: prepend 'resources/' to each element + return "<?php echo app(\Illuminate\Foundation\Vite::class)(array_map(fn(\$p) => 'resources/' . \$p, {$expr})); ?>"; + } + $path = trim($expr, "'\""); + return "<?php echo app(\Illuminate\Foundation\Vite::class)(\"resources/{$path}\"); ?>"; + }); + // Public routes: 60 requests per minute per IP RateLimiter::for('public', function (Request $request) { return Limit::perMinute(60)->by($request->ip()); diff --git a/package.json b/package.json index e280a32..1132cc3 100755 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "vite": "^5.0" }, "dependencies": { + "baseline-browser-mapping": "^2.10.18", + "caniuse-lite": "^1.0.30001787", "htmx.org": "^2.0.1", "jquery": "^3.7.1", "marked": "^17.0.4" diff --git a/public/4chan_query b/public/4chan_query Binary files differnew file mode 100644 index 0000000..ecb9b62 --- /dev/null +++ b/public/4chan_query diff --git a/public/js/app.js b/public/js/app.js deleted file mode 100644 index a8093be..0000000 --- a/public/js/app.js +++ /dev/null @@ -1,7 +0,0 @@ -import './bootstrap'; - -import Alpine from 'alpinejs'; - -window.Alpine = Alpine; - -Alpine.start(); diff --git a/public/js/writing_create.js b/public/js/writing_create.js deleted file mode 100644 index dc737f8..0000000 --- a/public/js/writing_create.js +++ /dev/null @@ -1,70 +0,0 @@ -let dom = {}; -let editorHistory = []; - -document.addEventListener('DOMContentLoaded', () => { - if (typeof marked === 'undefined') { - console.error("marked lib not loaded"); - return; - } - - initDOM(); - - marked.use({ breaks: true }); - - dom.inputText.addEventListener('input', () => { - dom.contentPreview.innerHTML = marked.parse(dom.inputText.value); - }); - - // Restore autosaved draft - const restoredContent = localStorage.getItem('writing_draft_content'); - if (restoredContent !== null) { - const expiration = parseInt(localStorage.getItem('writing_draft_expiration') || '0'); - if (expiration < Date.now()) { - localStorage.removeItem('writing_draft_content'); - localStorage.removeItem('writing_draft_expiration'); - } else { - dom.inputText.value = restoredContent; - dom.contentPreview.innerHTML = marked.parse(dom.inputText.value); - } - } - - // Periodically save contents to localStorage to prevent data loss - setInterval(() => { - localStorage.setItem('writing_draft_content', dom.inputText.value); - localStorage.setItem('writing_draft_expiration', Date.now() + 1000 * 3600 * 96); - }, 5000); - - // Clear draft on successful submit - document.getElementById('writing-form')?.addEventListener('submit', () => { - localStorage.removeItem('writing_draft_content'); - localStorage.removeItem('writing_draft_expiration'); - }); -}); - -function initDOM() { - dom.inputText = document.getElementById('input_content'); - dom.contentPreview = document.getElementById('content_preview'); - dom.findStr = document.getElementById('input_findstr'); - dom.replaceStr = document.getElementById('input_replacestr'); - dom.btnReplace = document.getElementById('button_replace'); - dom.btnUndo = document.getElementById('button_undo'); - dom.toggleMultiline = document.getElementById('input_multiline'); - - dom.btnReplace.addEventListener('click', performReplace); - dom.btnUndo.addEventListener('click', () => { - if (editorHistory.length > 0) { - dom.inputText.value = editorHistory.pop(); - dom.contentPreview.innerHTML = marked.parse(dom.inputText.value); - } - }); -} - -function performReplace() { - // Save current state for undo - editorHistory.push(dom.inputText.value); - - const flags = dom.toggleMultiline.checked ? 'gm' : 'g'; - const regex = new RegExp(dom.findStr.value, flags); - dom.inputText.value = dom.inputText.value.replace(regex, dom.replaceStr.value); - dom.contentPreview.innerHTML = marked.parse(dom.inputText.value); -};
\ No newline at end of file diff --git a/public/css/app.css b/resources/css/app.css index b5c61c9..b5c61c9 100644 --- a/public/css/app.css +++ b/resources/css/app.css diff --git a/public/css/home.css b/resources/css/home.css index ebbbb29..ebbbb29 100644 --- a/public/css/home.css +++ b/resources/css/home.css diff --git a/public/css/jstoys.css b/resources/css/jstoys.css index add19e2..add19e2 100644 --- a/public/css/jstoys.css +++ b/resources/css/jstoys.css diff --git a/public/css/normalize.css b/resources/css/normalize.css index 81c6f31..81c6f31 100644 --- a/public/css/normalize.css +++ b/resources/css/normalize.css diff --git a/public/css/presence.css b/resources/css/presence.css index 40a2d8f..c8da547 100644 --- a/public/css/presence.css +++ b/resources/css/presence.css @@ -327,4 +327,4 @@ background: #2563eb; color: #fff; border-color: #2563eb; -} +}
\ No newline at end of file diff --git a/public/css/reset.css b/resources/css/reset.css index 905b080..905b080 100644 --- a/public/css/reset.css +++ b/resources/css/reset.css diff --git a/public/css/skeleton.css b/resources/css/skeleton.css index c3fda1c..c3fda1c 100644 --- a/public/css/skeleton.css +++ b/resources/css/skeleton.css diff --git a/public/css/style.css b/resources/css/style.css index a5165fb..a5165fb 100644 --- a/public/css/style.css +++ b/resources/css/style.css diff --git a/public/css/writings.css b/resources/css/writings.css index 73f89fa..73f89fa 100644 --- a/public/css/writings.css +++ b/resources/css/writings.css diff --git a/resources/css/youtube.css b/resources/css/youtube.css new file mode 100644 index 0000000..9fed91e --- /dev/null +++ b/resources/css/youtube.css @@ -0,0 +1,198 @@ +/* YouTube Player Styles */ + +.input-section { + background: #272727; + padding: 20px; + border-radius: 8px; + margin-bottom: 20px; +} + +.input-group { + display: flex; + gap: 10px; + margin-bottom: 15px; +} + +#videoUrl { + flex: 1; + padding: 12px; + background: #121212; + border: 1px solid #3f3f3f; + border-radius: 4px; + color: #f1f1f1; + font-size: 14px; +} + +#videoUrl:focus { + outline: none; + border-color: #3ea6ff; +} + +#loadBtn { + padding: 12px 24px; + background: #3ea6ff; + color: #0f0f0f; + border: none; + border-radius: 4px; + font-weight: 600; + cursor: pointer; + font-size: 14px; + transition: background 0.2s; +} + +#loadBtn:hover { + background: #5eb3ff; +} + +#loadBtn:disabled { + background: #3f3f3f; + cursor: not-allowed; +} + +.format-selector { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.format-btn { + padding: 8px 16px; + background: #3f3f3f; + color: #f1f1f1; + border: 2px solid transparent; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + transition: all 0.2s; +} + +.format-btn:hover { + background: #4f4f4f; +} + +.format-btn.active { + border-color: #3ea6ff; + background: #1f1f1f; +} + +.video-section { + background: #000; + border-radius: 8px; + overflow: hidden; + margin-bottom: 20px; +} + +video { + width: 100%; + max-height: 70vh; + display: block; +} + +.video-info { + background: #272727; + padding: 15px; + border-radius: 8px; + margin-bottom: 20px; +} + +.video-info h5 { + font-size: 18px; + margin-bottom: 8px; + color: #f1f1f1; +} + +.video-info p { + color: #aaa; + font-size: 14px; + line-height: 1.5; +} + +.stream-url-section { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #3f3f3f; +} + +.stream-url-section label { + display: block; + font-size: 12px; + color: #aaa; + margin-bottom: 5px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.url-display { + display: flex; + gap: 10px; + margin-bottom: 5px; +} + +.url-display input[type="text"] { + flex: 1; + padding: 8px; + background: #1a1a1a; + border: 1px solid #3f3f3f; + border-radius: 4px; + color: #f1f1f1; + font-size: 12px; + font-family: 'Courier New', monospace; +} + +.url-display input[type="text"]:focus { + outline: none; + border-color: #3ea6ff; +} + +.copy-btn { + padding: 8px 16px; + background: #3ea6ff; + color: #0f0f0f; + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; + white-space: nowrap; +} + +.copy-btn:hover { + background: #5eb3ff; +} + +.url-type { + display: block; + font-size: 11px; + color: #888; + margin-top: 5px; +} + +.loading { + text-align: center; + padding: 40px; + color: #aaa; +} + +.error { + background: #3f1616; + color: #ff6b6b; + padding: 15px; + border-radius: 4px; + margin-bottom: 20px; +} + +.spinner { + border: 3px solid #3f3f3f; + border-top: 3px solid #3ea6ff; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + margin: 20px auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/resources/js/app.js b/resources/js/app.js index f0fa127..68f5beb 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,15 +1,10 @@ import htmx from 'htmx.org'; -import axios from 'axios'; import jQuery from 'jquery'; // Expose globally so inline scripts and Blade templates can use them window.htmx = htmx; -window.axios = axios; window.$ = jQuery; window.jQuery = jQuery; -// Set axios CSRF header from meta tag -const token = document.querySelector('meta[name="csrf-token"]'); -if (token) { - axios.defaults.headers.common['X-CSRF-TOKEN'] = token.getAttribute('content'); -} +const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); +window.csrfToken = csrfToken; diff --git a/public/js/blood.js b/resources/js/blood.js index c45663a..c45663a 100644 --- a/public/js/blood.js +++ b/resources/js/blood.js diff --git a/public/js/blood_gpu.js b/resources/js/blood_gpu.js index 19d88c4..19d88c4 100644 --- a/public/js/blood_gpu.js +++ b/resources/js/blood_gpu.js diff --git a/public/js/bootstrap.js b/resources/js/bootstrap.js index deb2e10..deb2e10 100644 --- a/public/js/bootstrap.js +++ b/resources/js/bootstrap.js diff --git a/public/js/echo.js b/resources/js/echo.js index 9349afa..9349afa 100644 --- a/public/js/echo.js +++ b/resources/js/echo.js diff --git a/public/js/hn.js b/resources/js/hn.js index d9201ec..d9201ec 100644 --- a/public/js/hn.js +++ b/resources/js/hn.js diff --git a/public/js/home.js b/resources/js/home.js index e69de29..e69de29 100644 --- a/public/js/home.js +++ b/resources/js/home.js diff --git a/public/js/lib.js b/resources/js/lib.js index 1067f7a..1067f7a 100644 --- a/public/js/lib.js +++ b/resources/js/lib.js diff --git a/public/js/main.js b/resources/js/main.js index b7673cc..ed5956e 100644 --- a/public/js/main.js +++ b/resources/js/main.js @@ -95,12 +95,14 @@ $(document).ready(async function() { }); async function getServerEnvVars(){ - await axios.get('/env').then((res)=>{ - env = res.data; - log(env); - }).catch((err)=>{ - log(err); - }); + await fetch('/env') + .then((res) => res.json()) + .then((data) => { + env = data; + log(env); + }).catch((err) => { + log(err); + }); } //initial setup such as hiding/showing certain things @@ -153,17 +155,18 @@ async function uploadFile() { formData.append('f[]', files[i], files[i].name); } - axios.post('/f', formData, {headers: {'Content-Type': 'multipart/form-data'}}) - .then((res)=>{ - console.log(res); + fetch('/f', {method: 'POST', headers: {'X-CSRF-TOKEN': window.csrfToken}, body: formData}) + .then((res) => res.json()) + .then((data) => { + console.log(data); domElems.fileUploadResult.innerHTML = 'File uploaded successfully<br>'; - res.data.files.forEach((f)=>{ + data.files.forEach((f) => { domElems.fileUploadResult.innerHTML += `<a href = "f/${f.filename}">${f.filename}</a><br>`; }); domElems.inputFile.value = null; domElems.buttonFileUpload.enabled = true; }) - .catch((err)=>{ + .catch((err) => { console.log(err); domElems.fileUploadResult.innerHTML = 'Error uploading file'; domElems.buttonFileUpload.enabled = true; diff --git a/public/js/marked.js b/resources/js/marked.js index 2eb226a..2eb226a 100644 --- a/public/js/marked.js +++ b/resources/js/marked.js diff --git a/public/js/socket.io.esm.min.js b/resources/js/socket.io.esm.min.js index 84cca0e..84cca0e 100644 --- a/public/js/socket.io.esm.min.js +++ b/resources/js/socket.io.esm.min.js diff --git a/public/js/webgpu.js b/resources/js/webgpu.js index b66f9ab..b66f9ab 100644 --- a/public/js/webgpu.js +++ b/resources/js/webgpu.js diff --git a/public/js/webgpu_gltf.js b/resources/js/webgpu_gltf.js index 28e9742..28e9742 100644 --- a/public/js/webgpu_gltf.js +++ b/resources/js/webgpu_gltf.js diff --git a/resources/js/writing_create.js b/resources/js/writing_create.js index cd44a66..9a036b1 100644 --- a/resources/js/writing_create.js +++ b/resources/js/writing_create.js @@ -1,17 +1,17 @@ -import $ from 'jquery'; import { marked } from 'marked'; -marked.use({ breaks: true }); - const dom = {}; const editorHistory = []; -$(document).ready(() => { +document.addEventListener('DOMContentLoaded', () => { + if (typeof marked === 'undefined') { + console.error("marked lib not loaded"); + return; + } + initDOM(); - dom.inputText.addEventListener('input', () => { - dom.contentPreview.innerHTML = marked.parse(dom.inputText.value); - }); + marked.use({ breaks: true }); // Restore autosaved draft const restoredContent = localStorage.getItem('writing_draft_content'); @@ -55,6 +55,9 @@ function initDOM() { dom.contentPreview.innerHTML = marked.parse(dom.inputText.value); } }); + dom.inputText.addEventListener('input', () => { + dom.contentPreview.innerHTML = marked.parse(dom.inputText.value); + }); } function performReplace() { diff --git a/public/js/writing_index.js b/resources/js/writing_index.js index de3159d..de3159d 100644 --- a/public/js/writing_index.js +++ b/resources/js/writing_index.js diff --git a/public/js/writing_show.js b/resources/js/writing_show.js index 92487eb..92487eb 100644 --- a/public/js/writing_show.js +++ b/resources/js/writing_show.js diff --git a/resources/js/youtube.js b/resources/js/youtube.js new file mode 100644 index 0000000..5160ff6 --- /dev/null +++ b/resources/js/youtube.js @@ -0,0 +1,276 @@ +// YouTube Player - Client-side JavaScript +// Following standard site.js pattern + +let env = {}; // Set by blade template +let cfg = {}; // User config from localStorage +let dom = { + input: {}, + button: {}, + section: {}, + video: {}, + info: {} +}; + +let selectedFormat = 'best'; + +// APP START HERE +$(document).ready(async function() { + console.log('YouTube Player Init'); + + // Core loop of the client application + // 1. Setup relationship with DOM and grab references to its elements + log('init DOM'); + await initDOM(); + + log('init cfg'); + await initCfg(); + + log('init services'); + await initServices(); + + log('YouTube Player Ready'); +}); + +// Gets user config from local storage if there is any +function initCfg() { + let localCfg = localStorage.getItem('youtube_cfg'); + if (localCfg) { + try { + cfg = JSON.parse(localCfg); + // Restore last used format if available + if (cfg.lastFormat) { + selectedFormat = cfg.lastFormat; + updateFormatButtons(); + } + } catch (e) { + cfg = {}; + } + } else { + cfg = {}; + } +} + +function initServices() { + // Attach event listeners + dom.button.load.addEventListener('click', loadVideo); + dom.input.videoUrl.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + loadVideo(); + } + }); + + // Format selector buttons + dom.button.formatBtns.forEach(btn => { + btn.addEventListener('click', function() { + selectFormat(this); + }); + }); + + // Copy URL button + dom.button.copyUrl.addEventListener('click', copyStreamUrl); +} + +function initDOM() { + dom.body = $('body')[0]; + + // Input elements + dom.input.videoUrl = $('#videoUrl')[0]; + + // Buttons + dom.button.load = $('#loadBtn')[0]; + dom.button.formatBtns = $('.format-btn').toArray(); + + // Sections + dom.section.error = $('#errorMsg')[0]; + dom.section.loading = $('#loadingMsg')[0]; + dom.section.videoSection = $('#videoSection')[0]; + dom.section.videoInfo = $('#videoInfo')[0]; + + // Video elements + dom.video.player = $('#videoPlayer')[0]; + + // Info elements + dom.info.title = $('#videoTitle')[0]; + dom.info.description = $('#videoDescription')[0]; + dom.info.streamUrl = $('#streamUrl')[0]; + dom.info.urlType = $('#urlType')[0]; + + // Copy button + dom.button.copyUrl = $('#copyUrlBtn')[0]; +} + +function selectFormat(btn) { + // Remove active class from all buttons + dom.button.formatBtns.forEach(b => b.classList.remove('active')); + + // Add active class to clicked button + btn.classList.add('active'); + + // Store selected format + selectedFormat = btn.dataset.format; + + // Save to config + cfg.lastFormat = selectedFormat; + localStorage.setItem('youtube_cfg', JSON.stringify(cfg)); +} + +function updateFormatButtons() { + dom.button.formatBtns.forEach(btn => { + if (btn.dataset.format === selectedFormat) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + }); +} + +async function loadVideo() { + const videoUrl = dom.input.videoUrl.value.trim(); + + if (!videoUrl) { + showError('Please enter a YouTube URL'); + return; + } + + // Validate YouTube URL + if (!isValidYouTubeUrl(videoUrl)) { + showError('Please enter a valid YouTube URL'); + return; + } + + hideError(); + showLoading(); + hideVideo(); + + try { + // Get video info and stream URL + const response = await axios.post(`/yt/info`, { + url: videoUrl, + format: selectedFormat + }); + + const data = response.data; + + if (data.error) { + throw new Error(data.error); + } + + // Display video + displayVideo(data); + + // Save to recent videos in config + saveToRecent(videoUrl, data.title); + + } catch (error) { + console.error('Error:', error); + showError(`Failed to load video: ${error.message}`); + } finally { + hideLoading(); + } +} + +function displayVideo(data) { + // Set video source + dom.video.player.src = data.stream_url; + + // Display video info + dom.info.title.textContent = data.title || 'Untitled Video'; + dom.info.description.textContent = data.description || 'No description available'; + + // Display stream URL + dom.info.streamUrl.value = data.stream_url; + + // Detect and display URL type + const urlType = detectStreamType(data.stream_url); + dom.info.urlType.textContent = urlType; + + // Show elements + dom.section.videoSection.hidden = false; + dom.section.videoInfo.hidden = false; + + // Auto-play + dom.video.player.play().catch(e => { + log('Auto-play prevented: ' + e.message); + }); +} + +function detectStreamType(url) { + if (url.includes('.m3u8')) { + return '📡 HLS Stream (.m3u8) - Adaptive bitrate streaming'; + } else if (url.includes('.mpd')) { + return '📡 DASH Stream (.mpd) - Adaptive bitrate streaming'; + } else if (url.includes('mime=video%2Fmp4')) { + return '📹 Progressive MP4 - Direct download'; + } else if (url.includes('googlevideo.com')) { + return '📹 Google Video CDN stream'; + } else { + return '🎥 Video stream'; + } +} + +function copyStreamUrl() { + dom.info.streamUrl.select(); + dom.info.streamUrl.setSelectionRange(0, 99999); // For mobile + + navigator.clipboard.writeText(dom.info.streamUrl.value).then(() => { + // Visual feedback + const originalText = dom.button.copyUrl.textContent; + dom.button.copyUrl.textContent = '✓ Copied!'; + dom.button.copyUrl.style.background = '#4caf50'; + + setTimeout(() => { + dom.button.copyUrl.textContent = originalText; + dom.button.copyUrl.style.background = ''; + }, 2000); + }).catch(err => { + log('Failed to copy: ' + err); + alert('Failed to copy URL. Please copy manually.'); + }); +} + +function isValidYouTubeUrl(url) { + const patterns = [ + /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)[\w-]+/, + /^(https?:\/\/)?(www\.)?youtube\.com\/embed\/[\w-]+/ + ]; + return patterns.some(pattern => pattern.test(url)); +} + +function saveToRecent(url, title) { + if (!cfg.recentVideos) { + cfg.recentVideos = []; + } + + // Add to beginning of array, limit to 10 recent + cfg.recentVideos.unshift({ url, title, timestamp: Date.now() }); + cfg.recentVideos = cfg.recentVideos.slice(0, 10); + + localStorage.setItem('youtube_cfg', JSON.stringify(cfg)); +} + +// UI Helper functions +function showError(message) { + dom.section.error.textContent = message; + dom.section.error.hidden = false; +} + +function hideError() { + dom.section.error.hidden = true; +} + +function showLoading() { + dom.section.loading.hidden = false; +} + +function hideLoading() { + dom.section.loading.hidden = true; +} + +function hideVideo() { + dom.section.videoSection.hidden = true; + dom.section.videoInfo.hidden = true; +} + +function log(msg, lvl = 1) { + console.log(msg); +} diff --git a/resources/js/youtube.js.bak b/resources/js/youtube.js.bak new file mode 100644 index 0000000..6aa2ca8 --- /dev/null +++ b/resources/js/youtube.js.bak @@ -0,0 +1,227 @@ +// YouTube Player - Client-side JavaScript +// Following standard site.js pattern + +let env = {}; // Set by blade template +let cfg = {}; // User config from localStorage +let dom = { + input: {}, + button: {}, + section: {}, + video: {}, + info: {} +}; + +let selectedFormat = 'best'; + +// APP START HERE +$(document).ready(async function() { + console.log('YouTube Player Init'); + + // Core loop of the client application + // 1. Setup relationship with DOM and grab references to its elements + log('init DOM'); + await initDOM(); + + log('init cfg'); + await initCfg(); + + log('init services'); + await initServices(); + + log('YouTube Player Ready'); +}); + +// Gets user config from local storage if there is any +function initCfg() { + let localCfg = localStorage.getItem('youtube_cfg'); + if (localCfg) { + try { + cfg = JSON.parse(localCfg); + // Restore last used format if available + if (cfg.lastFormat) { + selectedFormat = cfg.lastFormat; + updateFormatButtons(); + } + } catch (e) { + cfg = {}; + } + } else { + cfg = {}; + } +} + +function initServices() { + // Attach event listeners + dom.button.load.addEventListener('click', loadVideo); + dom.input.videoUrl.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + loadVideo(); + } + }); + + // Format selector buttons + dom.button.formatBtns.forEach(btn => { + btn.addEventListener('click', function() { + selectFormat(this); + }); + }); +} + +function initDOM() { + dom.body = $('body')[0]; + + // Input elements + dom.input.videoUrl = $('#videoUrl')[0]; + + // Buttons + dom.button.load = $('#loadBtn')[0]; + dom.button.formatBtns = $('.format-btn').toArray(); + + // Sections + dom.section.error = $('#errorMsg')[0]; + dom.section.loading = $('#loadingMsg')[0]; + dom.section.videoSection = $('#videoSection')[0]; + dom.section.videoInfo = $('#videoInfo')[0]; + + // Video elements + dom.video.player = $('#videoPlayer')[0]; + + // Info elements + dom.info.title = $('#videoTitle')[0]; + dom.info.description = $('#videoDescription')[0]; +} + +function selectFormat(btn) { + // Remove active class from all buttons + dom.button.formatBtns.forEach(b => b.classList.remove('active')); + + // Add active class to clicked button + btn.classList.add('active'); + + // Store selected format + selectedFormat = btn.dataset.format; + + // Save to config + cfg.lastFormat = selectedFormat; + localStorage.setItem('youtube_cfg', JSON.stringify(cfg)); +} + +function updateFormatButtons() { + dom.button.formatBtns.forEach(btn => { + if (btn.dataset.format === selectedFormat) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + }); +} + +async function loadVideo() { + const videoUrl = dom.input.videoUrl.value.trim(); + + if (!videoUrl) { + showError('Please enter a YouTube URL'); + return; + } + + // Validate YouTube URL + if (!isValidYouTubeUrl(videoUrl)) { + showError('Please enter a valid YouTube URL'); + return; + } + + hideError(); + showLoading(); + hideVideo(); + + try { + // Get video info and stream URL + const response = await axios.post('/yt/info', { + url: videoUrl, + format: selectedFormat + }); + + const data = response.data; + + if (data.error) { + throw new Error(data.error); + } + + // Display video + displayVideo(data); + + // Save to recent videos in config + saveToRecent(videoUrl, data.title); + + } catch (error) { + console.error('Error:', error); + showError(`Failed to load video: ${error.message}`); + } finally { + hideLoading(); + } +} + +function displayVideo(data) { + // Set video source + dom.video.player.src = data.stream_url; + + // Display video info + dom.info.title.textContent = data.title || 'Untitled Video'; + dom.info.description.textContent = data.description || 'No description available'; + + // Show elements + dom.section.videoSection.hidden = false; + dom.section.videoInfo.hidden = false; + + // Auto-play + dom.video.player.play().catch(e => { + log('Auto-play prevented: ' + e.message); + }); +} + +function isValidYouTubeUrl(url) { + const patterns = [ + /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)[\w-]+/, + /^(https?:\/\/)?(www\.)?youtube\.com\/embed\/[\w-]+/ + ]; + return patterns.some(pattern => pattern.test(url)); +} + +function saveToRecent(url, title) { + if (!cfg.recentVideos) { + cfg.recentVideos = []; + } + + // Add to beginning of array, limit to 10 recent + cfg.recentVideos.unshift({ url, title, timestamp: Date.now() }); + cfg.recentVideos = cfg.recentVideos.slice(0, 10); + + localStorage.setItem('youtube_cfg', JSON.stringify(cfg)); +} + +// UI Helper functions +function showError(message) { + dom.section.error.textContent = message; + dom.section.error.hidden = false; +} + +function hideError() { + dom.section.error.hidden = true; +} + +function showLoading() { + dom.section.loading.hidden = false; +} + +function hideLoading() { + dom.section.loading.hidden = true; +} + +function hideVideo() { + dom.section.videoSection.hidden = true; + dom.section.videoInfo.hidden = true; +} + +function log(msg, lvl = 1) { + console.log(msg); +} diff --git a/resources/views/graphics.html b/resources/views/.off/graphics.html index d6e8ae7..d6e8ae7 100644 --- a/resources/views/graphics.html +++ b/resources/views/.off/graphics.html diff --git a/resources/views/marked.blade.php b/resources/views/.off/marked.blade.php index f1deefc..823900c 100644 --- a/resources/views/marked.blade.php +++ b/resources/views/.off/marked.blade.php @@ -7,9 +7,9 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!--<link rel="stylesheet" type="text/css" href="static/reset.css">--> <link rel="stylesheet" type="text/css" href="style.css"> - @vite('resources/js/app.js') + @v('js/app.js') + @v('js/marked.js') <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> - <script src="/js/marked.js"></script> <!--<base href = "http://192.168.4.32:9002/">--> <!--<base href = "http://localhost:9002/">--> <title>markup playground</title> diff --git a/resources/views/4.blade.php b/resources/views/4.blade.php index f23d1e1..9e32cf5 100755 --- a/resources/views/4.blade.php +++ b/resources/views/4.blade.php @@ -3,8 +3,9 @@ <main> <h4>Search Current 4chan Posts</h4> <form action = "/4" method = "POST"> - query <input type = "text" id = "query" /> - board <input type = "text" id = "board" /> + @csrf + query <input type = "text" name = "query" /> + board <input type = "text" name = "board" /> <button type = "submit">Search</button> </form> </main> diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index c993212..d6ac6ec 100755 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -1,7 +1,6 @@ @extends('template') @section('scripts') -<script type = "module" src = "/js/main.js"></script> -<script src = "js/hn.js" defer></script> +@v(['js/main.js', 'js/hn.js']) @endsection @section('content') @@ -70,7 +69,7 @@ UNDER CONSTRUCTION; APOLOGIES FOR LACKING FUNCTIONALITY. </div> </section> <footer> - <p>Last updated March 6th, 2026</p> + <p>Last updated April 12th, 2026</p> </footer> @endsection </html> diff --git a/resources/views/presence/index.blade.php b/resources/views/presence/index.blade.php index b9765fa..bf05cc2 100644 --- a/resources/views/presence/index.blade.php +++ b/resources/views/presence/index.blade.php @@ -1,6 +1,6 @@ @extends('template') @section('head') -<link rel="stylesheet" href="/css/presence.css"> +@v(['css/presence.css']) @endsection @section('content') diff --git a/resources/views/presence/show.blade.php b/resources/views/presence/show.blade.php index 0b23bbe..a3f19b9 100644 --- a/resources/views/presence/show.blade.php +++ b/resources/views/presence/show.blade.php @@ -1,6 +1,5 @@ @extends('template') @section('head') -<link rel="stylesheet" href="/css/presence.css"> @endsection @section('content') diff --git a/resources/views/template.blade.php b/resources/views/template.blade.php index 5d41e61..61a7a59 100755 --- a/resources/views/template.blade.php +++ b/resources/views/template.blade.php @@ -5,7 +5,8 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TG</title> <meta name="csrf-token" content="{{ csrf_token() }}"> - <link rel="stylesheet" href="/css/home.css"> + @v('css/home.css') + <!--<link rel="stylesheet" href="/css/home.css">--> @yield('head') </head> <body> diff --git a/resources/views/writings/create.blade.php b/resources/views/writings/create.blade.php index c095b5a..5e7281d 100644 --- a/resources/views/writings/create.blade.php +++ b/resources/views/writings/create.blade.php @@ -2,9 +2,9 @@ @section('head') <meta charset="UTF-8"> -@vite('resources/js/writing_create.js') -<link rel="stylesheet" href="/css/home.css"> -<link rel="stylesheet" href="/css/writings.css"> +@v('js/writing_create.js') +@v('css/home.css') +@v('css/writings.css') @endsection @section('content') diff --git a/resources/views/writings/edit.blade.php b/resources/views/writings/edit.blade.php index e442b5d..10a9e92 100644 --- a/resources/views/writings/edit.blade.php +++ b/resources/views/writings/edit.blade.php @@ -1,9 +1,10 @@ @extends('template') @section('head') -<link rel="stylesheet" href="/css/writings.css"> +@v('css/writings.css') + <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> -<script type = "module" src = "/js/writing_show.js"></script> +@v('js/writing_show.js') @endsection @section('content') diff --git a/resources/views/writings/index.blade.php b/resources/views/writings/index.blade.php index 3f5d0db..7e12333 100644 --- a/resources/views/writings/index.blade.php +++ b/resources/views/writings/index.blade.php @@ -1,7 +1,7 @@ @extends('template') @section('head') +@v('js/writing_index.js') <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> -<script type = "module" src = "/js/writing_index.js"></script> @endsection @section('content') <main> diff --git a/resources/views/writings/show.blade.php b/resources/views/writings/show.blade.php index 4b191ad..e55fb4b 100644 --- a/resources/views/writings/show.blade.php +++ b/resources/views/writings/show.blade.php @@ -1,9 +1,9 @@ @extends('template') @section('head') -<link rel="stylesheet" href="/css/writings.css"> +@v('css/writings.css') <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> -<script type = "module" src = "/js/writing_show.js"></script> +@v('js/writing_show.js') @endsection @section('nav') diff --git a/routes/web.php b/routes/web.php index e2cf097..df3f526 100755 --- a/routes/web.php +++ b/routes/web.php @@ -80,8 +80,8 @@ Route::middleware(['auth', 'throttle:admin', \App\Http\Middleware\Admin::class]) Route::get('/admin', [AdminController::class, 'index'])->name('admin'); Route::post('/l/import', [LinkController::class, 'import'])->name('l.import'); Route::get('/env', [SiteController::class, 'env']); - Route::get('/4', [SiteController::class, 'search4chan']); }); +Route::post('/4', [SiteController::class, 'search4chan']); // Public read-only routes Route::resource('w', WritingController::class)->only(['index', 'show']); @@ -102,7 +102,7 @@ Route::post('/update-session', function(Request $req){ require __DIR__.'/auth.php'; -Route::get('/toy/{v?}', function($v = null){ +/*Route::get('/toy/{v?}', function($v = null){ if (is_null($v)){ return view('toys.index'); } @@ -114,7 +114,7 @@ Route::get('/toy/{v?}', function($v = null){ abort(404); } return view('toys.'.$v); -}); +});*/ // Audio stream - requires auth to prevent abuse Route::get('/stream/audio', function() { @@ -146,7 +146,7 @@ Route::get('/newtab', function() { // Catch-all: only allow specific whitelisted views Route::get('/{v}', function($v){ - $allowed = ['notes', 'kyanite', 'marked', 'v']; + $allowed = ['notes', 'kyanite', 'marked', 'v', '4']; if (!in_array($v, $allowed)) { abort(404); } diff --git a/vite.config.js b/vite.config.js index 9f67760..35a4549 100755 --- a/vite.config.js +++ b/vite.config.js @@ -6,13 +6,24 @@ export default defineConfig({ laravel({ input: [ 'resources/js/app.js', + 'resources/js/main.js', + 'resources/js/hn.js', 'resources/js/writing_create.js', + 'resources/css/writings.css', + 'resources/css/app.css', + 'resources/css/home.css', + 'resources/css/presence.css', ], refresh: true, }), ], server: { - host: '192.168.1.171', + host: '127.0.0.1', cors: true, + }, + resolve: { + alias: { + '@': 'resources' + } } }); |
