From 49048003651f989d6cbbf4f4b9a73115833a2399 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 26 Apr 2026 08:21:38 +0200 Subject: [PATCH] bug-fixes --- assets/app.js | 3 - assets/bootstrap.js | 10 +- .../article_comments_controller.js | 4 +- .../article_highlight_controller.js | 290 ------------------ .../controllers/service-worker_controller.js | 4 +- assets/styles/app.css | 7 +- assets/styles/article.css | 101 +----- assets/styles/event.css | 2 +- assets/styles/layout.css | 44 ++- assets/styles/theme.css | 11 +- src/Command/PrewarmCommand.php | 7 +- src/Controller/ArticleController.php | 61 ---- src/Service/ArticleBodyHighlightInjector.php | 10 +- src/Service/MagazineContentService.php | 3 +- .../ArticleHighlightMetaHead.html.twig | 8 - .../Organisms/HomeHighlightsAside.html.twig | 2 +- templates/pages/article.html.twig | 23 +- translations/messages.en.yaml | 2 - 18 files changed, 79 insertions(+), 513 deletions(-) delete mode 100644 assets/controllers/article_highlight_controller.js delete mode 100644 templates/components/Molecules/ArticleHighlightMetaHead.html.twig diff --git a/assets/app.js b/assets/app.js index e3e3724..a17c164 100644 --- a/assets/app.js +++ b/assets/app.js @@ -19,6 +19,3 @@ import './styles/form.css'; import './styles/notice.css'; import './styles/spinner.css'; import './styles/a2hs.css'; - - -console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); diff --git a/assets/bootstrap.js b/assets/bootstrap.js index d564fdb..71f0f99 100644 --- a/assets/bootstrap.js +++ b/assets/bootstrap.js @@ -2,9 +2,10 @@ import { startStimulusApp } from '@symfony/stimulus-bundle'; import ArticleCommentsController from './controllers/article_comments_controller.js'; import CommentReplyController from './controllers/comment_reply_controller.js'; import CopyTextController from './controllers/copy_text_controller.js'; -import ArticleHighlightController from './controllers/article_highlight_controller.js'; - const app = startStimulusApp(); +if (typeof app.debug === 'boolean') { + app.debug = false; +} // Ensure lazy comment loader is registered (Asset Mapper discovery can miss new files until rebuild). try { @@ -22,8 +23,3 @@ try { } catch { /* already registered by the bundle */ } -try { - app.register('article-highlight', ArticleHighlightController); -} catch { - /* already registered by the bundle */ -} diff --git a/assets/controllers/article_comments_controller.js b/assets/controllers/article_comments_controller.js index c5fa54c..4cc5fe2 100644 --- a/assets/controllers/article_comments_controller.js +++ b/assets/controllers/article_comments_controller.js @@ -79,9 +79,9 @@ export default class extends Controller { } const ms = Math.round(performance.now() - t0); if (attempt > 1) { - console.info(`[article-comments] fragment OK in ${ms}ms (after ${attempt} attempts)`, this.urlValue); + console.debug(`[article-comments] fragment OK in ${ms}ms (after ${attempt} attempts)`, this.urlValue); } else { - console.info(`[article-comments] fragment OK in ${ms}ms`, this.urlValue); + console.debug(`[article-comments] fragment OK in ${ms}ms`, this.urlValue); } window.clearTimeout(timer); return; diff --git a/assets/controllers/article_highlight_controller.js b/assets/controllers/article_highlight_controller.js deleted file mode 100644 index b5c4090..0000000 --- a/assets/controllers/article_highlight_controller.js +++ /dev/null @@ -1,290 +0,0 @@ -import { Controller } from '@hotwired/stimulus'; - -const MARK_SEL = 'mark.article-body-highlight'; - -/** - * In-article NIP-84 marks: show popover on hover / click; #h-<64-hex> scrolls and focuses the mark. - */ -export default class extends Controller { - static targets = ['article', 'meta', 'popover', 'popoverInner']; - - connect() { - this._metaById = {}; - if (this.hasMetaTarget) { - try { - this._metaById = JSON.parse(this.metaTarget.textContent || '{}'); - } catch { - this._metaById = {}; - } - } - this._pinnedId = null; - this._openId = null; - this._hoverLeaveTimer = null; - this._onHash = () => { - this._scrollToHash(); - }; - this._onScrollReposition = () => { - if (this._openId && this.hasPopoverTarget) { - const a = this._getMarkByEventId(this._openId); - if (a) { - this._placePopover(a); - } - } - }; - this._onPopoverPointerEnter = () => { - this._clearHoverLeaveTimer(); - }; - this._onPopoverPointerLeave = () => { - if (!this._pinnedId) { - this._hoverLeaveTimer = setTimeout(() => { - this._hoverLeaveTimer = null; - this._hideUnpinnedPopover(); - }, 200); - } - }; - - this._onMarkPointerEnter = (e) => { - const a = e.target && e.target.closest && e.target.closest(MARK_SEL); - if (!a) { - return; - } - this._clearHoverLeaveTimer(); - this._openForMark(a, false); - }; - this._onMarkPointerLeave = (e) => { - const a = e.target && e.target.closest && e.target.closest(MARK_SEL); - if (!a) { - return; - } - this._onMarkLeave(a); - }; - this._onMarkClick = (e) => { - const a = e.target && e.target.closest && e.target.closest(MARK_SEL); - if (!a) { - return; - } - e.preventDefault(); - e.stopPropagation(); - const id = (a.getAttribute('data-event-id') || '').toLowerCase(); - if (this._pinnedId === id) { - this._closePinned(); - this._hideUnpinnedPopover(); - return; - } - this._openForMark(a, true); - }; - this._onMarkFocus = (e) => { - const t = e.target; - if (!t || !t.classList || !t.classList.contains('article-body-highlight')) { - return; - } - this._openForMark(t, false); - }; - this._onMarkKeydown = (e) => { - if (e.key !== 'Enter' && e.key !== ' ') { - return; - } - const t = e.target; - if (!t || !t.classList || !t.classList.contains('article-body-highlight')) { - return; - } - e.preventDefault(); - this._openForMark(t, true); - }; - - if (this.hasArticleTarget) { - this.articleTarget.addEventListener('pointerenter', this._onMarkPointerEnter, true); - this.articleTarget.addEventListener('pointerleave', this._onMarkPointerLeave, true); - this.articleTarget.addEventListener('click', this._onMarkClick, true); - this.articleTarget.addEventListener('focusin', this._onMarkFocus, true); - this.articleTarget.addEventListener('keydown', this._onMarkKeydown, true); - } - if (this.hasPopoverTarget) { - this.popoverTarget.addEventListener('pointerenter', this._onPopoverPointerEnter); - this.popoverTarget.addEventListener('pointerleave', this._onPopoverPointerLeave); - } - window.addEventListener('scroll', this._onScrollReposition, true); - window.addEventListener('resize', this._onScrollReposition); - this._scrollToHash(); - window.addEventListener('hashchange', this._onHash); - } - - disconnect() { - window.removeEventListener('hashchange', this._onHash); - window.removeEventListener('scroll', this._onScrollReposition, true); - window.removeEventListener('resize', this._onScrollReposition); - if (this.hasArticleTarget) { - this.articleTarget.removeEventListener('pointerenter', this._onMarkPointerEnter, true); - this.articleTarget.removeEventListener('pointerleave', this._onMarkPointerLeave, true); - this.articleTarget.removeEventListener('click', this._onMarkClick, true); - this.articleTarget.removeEventListener('focusin', this._onMarkFocus, true); - this.articleTarget.removeEventListener('keydown', this._onMarkKeydown, true); - } - if (this.hasPopoverTarget) { - this.popoverTarget.removeEventListener('pointerenter', this._onPopoverPointerEnter); - this.popoverTarget.removeEventListener('pointerleave', this._onPopoverPointerLeave); - } - this._clearHoverLeaveTimer(); - } - - closeOnOutside(event) { - if (!this.hasPopoverTarget) { - return; - } - if (!this._pinnedId && !this._openId) { - return; - } - const t = event.target; - if (this.popoverTarget.contains(t)) { - return; - } - if (t && t.closest && t.closest('mark.article-body-highlight')) { - return; - } - if (this._pinnedId) { - this._closePinned(); - } else { - this._hideUnpinnedPopover(); - } - } - - onKeydown(event) { - if (event.key === 'Escape') { - this._closePinned(); - this._hideUnpinnedPopover(); - } - } - - closePopover() { - this._closePinned(); - this._hideUnpinnedPopover(); - } - - _onMarkLeave(mark) { - this._clearHoverLeaveTimer(); - if (this._pinnedId) { - return; - } - this._hoverLeaveTimer = setTimeout(() => { - this._hoverLeaveTimer = null; - if (this._openId === (mark.getAttribute('data-event-id') || '').toLowerCase()) { - this._hideUnpinnedPopover(); - } - }, 200); - } - - _clearHoverLeaveTimer() { - if (this._hoverLeaveTimer) { - clearTimeout(this._hoverLeaveTimer); - this._hoverLeaveTimer = null; - } - } - - _getMarkByEventId(id) { - if (!id) { - return null; - } - return ( - this.element.querySelector(`#highlight-${id}`) || - this.element.querySelector(`mark.article-body-highlight[data-event-id="${CSS.escape(id)}"]`) - ); - } - - _openForMark(mark, pin) { - if (!this.hasPopoverTarget || !this.hasPopoverInnerTarget) { - return; - } - const id = (mark.getAttribute('data-event-id') || '').toLowerCase(); - if (!id || 64 !== id.length) { - return; - } - const meta = this._metaById[id]; - if (!meta) { - return; - } - this._openId = id; - if (pin) { - this._pinnedId = id; - } else { - this._clearHoverLeaveTimer(); - } - const head = meta.headHtml || ''; - const body = (meta.bodyHtml || '').trim(); - this.popoverInnerTarget.innerHTML = - head + (body !== '' ? `
${body}
` : ''); - this._placePopover(mark); - this.popoverTarget.hidden = false; - } - - _placePopover(anchor) { - if (!this.hasPopoverTarget) { - return; - } - const p = this.popoverTarget; - p.style.position = 'fixed'; - p.style.zIndex = '200'; - p.style.left = '0'; - p.style.top = '0'; - const r = anchor.getBoundingClientRect(); - p.hidden = false; - const pr = p.getBoundingClientRect(); - let left = r.left + r.width / 2 - pr.width / 2; - let top = r.bottom + 8; - if (left < 8) { - left = 8; - } - if (left + pr.width > window.innerWidth - 8) { - left = Math.max(8, window.innerWidth - 8 - pr.width); - } - if (top + pr.height > window.innerHeight - 8) { - top = Math.max(8, r.top - 8 - pr.height); - } - p.style.left = `${Math.round(left)}px`; - p.style.top = `${Math.round(top)}px`; - } - - _hideUnpinnedPopover() { - this._openId = null; - this._clearHoverLeaveTimer(); - if (this._pinnedId) { - return; - } - if (this.hasPopoverTarget) { - this.popoverTarget.hidden = true; - } - if (this.hasPopoverInnerTarget) { - this.popoverInnerTarget.innerHTML = ''; - } - } - - _closePinned() { - this._pinnedId = null; - if (this.hasPopoverTarget) { - this.popoverTarget.hidden = true; - } - if (this.hasPopoverInnerTarget) { - this.popoverInnerTarget.innerHTML = ''; - } - this._openId = null; - } - - _scrollToHash() { - const raw = window.location.hash || ''; - if (!raw.startsWith('#h-')) { - return; - } - const id = raw.slice(3).toLowerCase().replace(/[^0-9a-f]/g, ''); - if (id.length !== 64) { - return; - } - const el = this.element.querySelector(`#highlight-${id}`) || this._getMarkByEventId(id); - if (!el) { - return; - } - el.classList.remove('article-body-highlight--target'); - void el.offsetWidth; - el.classList.add('article-body-highlight--target'); - el.scrollIntoView({ block: 'center', behavior: 'smooth' }); - this._openForMark(el, false); - } -} diff --git a/assets/controllers/service-worker_controller.js b/assets/controllers/service-worker_controller.js index b2bba42..9fe7d7e 100644 --- a/assets/controllers/service-worker_controller.js +++ b/assets/controllers/service-worker_controller.js @@ -4,7 +4,9 @@ export default class extends Controller { connect() { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') - .then(reg => console.log('SW registered:', reg)) + .then(() => { + /* optional: console.debug('SW registered') */ + }) .catch(err => console.error('SW failed:', err)); } } diff --git a/assets/styles/app.css b/assets/styles/app.css index c4ad10c..64ebd22 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -243,7 +243,8 @@ svg.icon { font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; - color: hsl(var(--tile-hue) 26% 32%); + /* Blend hue toward body mid-gray so every tile hue stays ≥4.5:1 on dark head strip */ + color: color-mix(in srgb, hsl(var(--tile-hue) 26% 50%) 38%, var(--color-text-mid) 62%); line-height: 1.35; } @@ -355,7 +356,7 @@ svg.icon { font-size: 0.78rem; font-weight: 400; line-height: 1.35; - color: color-mix(in srgb, var(--color-text-mid) 48%, var(--color-bg) 52%); + color: var(--color-text-mid); margin: 0.15rem 0 0.45rem; } @@ -385,7 +386,7 @@ svg.icon { font-size: 0.78rem; font-weight: 400; line-height: 1.35; - color: color-mix(in srgb, var(--color-text-mid) 48%, var(--color-bg) 52%); + color: var(--color-text-mid); } .article-list .metadata p { diff --git a/assets/styles/article.css b/assets/styles/article.css index c8af856..c9c259c 100644 --- a/assets/styles/article.css +++ b/assets/styles/article.css @@ -422,79 +422,7 @@ background: #fdecec; } -/* NIP-84: kind-9802 marks in .article-main + client popover (no separate thread list) */ -.article-body-highlight { - cursor: pointer; - scroll-margin-top: 6rem; -} - -.article-body-highlight--target { - box-shadow: none; - background: transparent; - text-decoration: underline; - text-decoration-color: color-mix(in srgb, var(--color-text) 35%, transparent); - text-underline-offset: 0.12em; - transition: text-decoration-color 0.25s ease; -} - -.article-body-highlight:focus-visible { - outline: 2px solid var(--color-focus-ring, var(--color-primary)); - outline-offset: 2px; -} - -.article-body-highlight__popover[hidden] { - display: none; -} - -.article-body-highlight__popover { - position: fixed; - z-index: 200; - max-width: min(24rem, calc(100vw - 1.5rem)); - margin: 0; - padding: 0.65rem 2rem 0.85rem 0.85rem; - border: 1px solid color-mix(in srgb, var(--color-border) 75%, var(--color-bg) 25%); - border-radius: 0.45rem; - color: var(--color-text); - /* Slightly above page bg so the card reads as a raised surface */ - background: color-mix(in srgb, var(--color-bg) 88%, #fff 12%); - box-shadow: 0 0.4rem 1.25rem rgba(0, 0, 0, 0.14); - pointer-events: auto; -} - -.article-body-highlight__close { - position: absolute; - top: 0.2rem; - right: 0.35rem; - margin: 0; - border: 0; - padding: 0.15rem 0.4rem; - line-height: 1; - font-size: 1.35rem; - color: var(--color-text-mid); - background: transparent; - border-radius: 0.2rem; - cursor: pointer; -} - -.article-body-highlight__close:hover, -.article-body-highlight__close:focus-visible { - color: var(--color-text); - outline: 2px solid var(--color-focus-ring, var(--color-primary)); -} - -.article-body-highlight__inner { - position: relative; -} - -.article-body-highlight__head { - display: flex; - flex-wrap: wrap; - align-items: baseline; - justify-content: space-between; - gap: 0.4rem 0.75rem; - margin: 0; - font-size: 0.86rem; -} +/* NIP-84: kind-9802 marks in .article-main (fragment id highlight- for deep links) */ /* Full `context` quote + optional on the `content` substring (body copy, not a box) */ .user-highlight__body { @@ -505,34 +433,25 @@ font-family: var(--main-body-font), serif; } -/* In-flow article body: interactive but no highlighter fill (cards below own the color) */ -.article-main mark.user-highlight__marker { - margin: 0; - padding: 0; - border-radius: 0; - font: inherit; - line-height: inherit; - color: inherit; - background: transparent; - box-shadow: none; - box-decoration-break: clone; - -webkit-box-decoration-break: clone; -} - -/* Popover + home aside: full context where present, `content` visibly marked */ -.article-body-highlight__body mark.user-highlight__marker, +/* In-flow + aside: same NIP-84 mark treatment; scroll-margin in article for #highlight-… links */ +.article-main mark.user-highlight__marker, .home-aside-highlights__quote--html mark.user-highlight__marker { margin: 0; padding: 0.08em 0.1em 0.12em; border-radius: 0.12em; font: inherit; line-height: inherit; - color: inherit; - background: color-mix(in srgb, #7ad67a 38%, #f0e8a0 62%); + color: var(--color-highlight-mark-fg); + background: color-mix(in srgb, #7ad67a 30%, #f0e8a0 70%); + box-shadow: none; box-decoration-break: clone; -webkit-box-decoration-break: clone; } +.article-main mark.user-highlight__marker { + scroll-margin-top: calc(var(--site-fixed-header-offset, 140px) + 0.75rem); +} + /* When `content` is not a substring of `context` (rare) */ .user-highlight__marker-orphan { margin: 0.5rem 0 0; diff --git a/assets/styles/event.css b/assets/styles/event.css index 3af21d6..65e1e83 100644 --- a/assets/styles/event.css +++ b/assets/styles/event.css @@ -98,7 +98,7 @@ font-size: 0.78rem; font-weight: 400; line-height: 1.35; - color: color-mix(in srgb, var(--color-text-mid) 50%, var(--color-bg) 50%); + color: var(--color-text-mid); } .event-page a:focus-visible { diff --git a/assets/styles/layout.css b/assets/styles/layout.css index 2416c7c..969531f 100644 --- a/assets/styles/layout.css +++ b/assets/styles/layout.css @@ -463,8 +463,18 @@ a.nostr-share-menu__action { } /* Main content */ +:root { + /* Clears fixed #site-header; keep in sync with main margin-top per breakpoint below. */ + --site-fixed-header-offset: 140px; +} + +/* #:target / in-page links: scroll position leaves room under the fixed bar (scroll-margin on inline is unreliable alone). */ +html { + scroll-padding-top: calc(var(--site-fixed-header-offset) + 0.75rem); +} + main { - margin-top: 140px; + margin-top: var(--site-fixed-header-offset); flex-grow: 1; min-width: 0; /* flex item: allow shrinking below wide images / intrinsic min-content */ padding: 1em; @@ -480,19 +490,19 @@ main { } @media (min-width: 1025px) { - /* Match extra header padding-top so content and menu clear the fixed bar */ - main { - margin-top: 152px; + :root { + --site-fixed-header-offset: 152px; } + /* Match extra header padding-top so content and menu clear the fixed bar */ /* In-flow left column: