Add TypeScript migration, image resizing, media upload UX, and multimedia support
All checks were successful
Deploy to NAS / deploy (push) Successful in 2m20s

- Migrate static JS to TypeScript (static-ts/ → compiled to internal/api/static/)
- Add image resizing on upload: JPEG/PNG/WebP scaled to max 1920px at quality 80
- Extract shared upload logic into upload.go (saveUpload, saveResizedImage, saveResizedWebP)
- Add POST /media endpoint for drag-drop/paste media uploads with markdown ref return
- Add background music player with video/audio coordination (autoplay.ts)
- Add global nav, public feed, hashtags, visibility, Markdown rendering for entries
- Add Dockerfile stage for TypeScript compilation (static-ts-builder)
- Add goldmark, disintegration/imaging, golang.org/x/image dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christoph K.
2026-04-09 23:03:04 +02:00
parent 8eef933573
commit 17186e7b64
24 changed files with 890 additions and 179 deletions

View File

@@ -0,0 +1,103 @@
"use strict";
(function () {
'use strict';
/* ── Background player ───────────────────────────────────── */
const bgAudio = new Audio();
let bgPlaying = false;
let bgBar = null;
let bgTitle = null;
let bgPlayBtn = null;
function createBgBar() {
var _a;
if (bgBar)
return;
bgBar = document.createElement('div');
bgBar.id = 'bg-bar';
bgBar.innerHTML =
'<span id="bg-title"></span>' +
'<button id="bg-play" aria-label="Abspielen">▶</button>' +
'<button id="bg-close" aria-label="Schließen">✕</button>';
document.body.appendChild(bgBar);
bgTitle = document.getElementById('bg-title');
bgPlayBtn = document.getElementById('bg-play');
bgPlayBtn.addEventListener('click', function () {
if (bgAudio.paused)
void bgAudio.play();
else
bgAudio.pause();
});
(_a = document.getElementById('bg-close')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', function () {
bgAudio.pause();
if (bgBar)
bgBar.style.display = 'none';
});
bgAudio.addEventListener('play', function () { if (bgPlayBtn)
bgPlayBtn.textContent = '⏸'; });
bgAudio.addEventListener('pause', function () { if (bgPlayBtn)
bgPlayBtn.textContent = '▶'; });
bgAudio.addEventListener('ended', function () { if (bgPlayBtn)
bgPlayBtn.textContent = '▶'; });
}
function sendToBg(src, title) {
createBgBar();
if (bgBar)
bgBar.style.display = 'flex';
bgAudio.src = src;
if (bgTitle)
bgTitle.textContent = title;
void bgAudio.play();
}
// Attach "♪" button to every inline audio player
document.querySelectorAll('audio.media-audio').forEach(function (a) {
const btn = document.createElement('button');
btn.className = 'btn-bg-music';
btn.textContent = '♪ Hintergrundmusik';
btn.type = 'button';
const title = a.title || a.src.split('/').pop() || a.src;
btn.addEventListener('click', function () { sendToBg(a.src, title); });
a.insertAdjacentElement('afterend', btn);
});
/* ── Video autoplay + coordination ──────────────────────── */
const obs = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
const v = e.target;
if (e.isIntersecting) {
void v.play();
}
else {
v.pause();
}
});
}, { threshold: 0.3 });
document.querySelectorAll('video.media-embed').forEach(function (v) {
v.muted = true;
v.loop = true;
v.setAttribute('playsinline', '');
obs.observe(v);
// User unmutes → pause background music
v.addEventListener('volumechange', function () {
if (!v.muted && !v.paused) {
bgPlaying = !bgAudio.paused;
bgAudio.pause();
}
// Video muted again → resume background
if (v.muted && bgPlaying) {
void bgAudio.play();
bgPlaying = false;
}
});
// Video pauses or ends → resume background if it was playing
v.addEventListener('pause', function () {
if (bgPlaying) {
void bgAudio.play();
bgPlaying = false;
}
});
v.addEventListener('ended', function () {
if (bgPlaying) {
void bgAudio.play();
bgPlaying = false;
}
});
});
})();