Commit Graph

18 Commits

Author SHA1 Message Date
Jakub Zych
d64fe47adf fix: path-form listing routes use the SEO module's resolved pagination
- journalPaginationSource answers runtimeConfig.public.seo.pagination (the
  SEO module's option with its defaults), else the raw seo.pagination
- the module computes the paged routes inside its extendPages callback, so
  an app with only form: 'path' gets /blog/strona/:page and
  /blog/page/:page (L4 WR-03)
2026-11-04 22:53:13 +01:00
Jakub Zych
839db5156c fix: post pages keep og:type article and their article times
- journalArticleMeta(post) answers og:type article and the publish and
  update times; PostPage always sets them, the CMS-owned keys keep the
  no-answer fallback (L4 WR-02)
- expect-acme.txt: one og:type article and the article times with a CMS
  answer (heading-on) and without one (uncategorised-note)
2026-11-04 22:51:31 +01:00
Jakub Zych
c6ffa6ca13 fix: export JournalImageVariant from one auto-imported file
- types.ts re-exported the interface srcset.ts declares, so every build of
  an app with the layer warned Duplicated imports "JournalImageVariant"
- a test checks that every name of app/utils and app/composables has one
  home
2026-11-04 19:28:38 +01:00
Jakub Zych
1080a3f0c4 feat: WebP and srcset for featured images
- JournalFeaturedImage renders <picture> from featured_images[].variants:
  a WebP source and the original format as srcset fallback, the
  original's width and height, alt; lazy in cards, eager with
  fetchpriority high on the post page; unchanged without variants
- app/utils/srcset.ts (imageSrcsets) with node tests
- acme fixtures and SSR expectations for both cases
2026-11-04 17:10:30 +01:00
Jakub Zych
adcea4d538 feat: category neighbours on the post page
The post page links previous_in_category and next_in_category (the
Journal's opt-in scored related mode) as server-rendered links with the
title in the anchor text when the Journal answers one, else the
site-wide previous_post and next_post (postNeighbours). Types, a fixture
post and expectation lines for both cases.
2026-11-04 14:15:22 +01:00
Jakub Zych
642c25a0d0 feat: ItemList nodes on listing pages
The index, category, tag and author pages add an ItemList node
(@id <page URL>#itemlist, a ListItem with the absolute URL of each post
shown, positions continuing across pages) through vue-seo-layer's
useSchemaNode, so the page keeps one JSON-LD script with one @graph
(useJournalItemList, journalItemList).
2026-11-04 13:55:55 +01:00
Jakub Zych
98904b8fbd feat: breadcrumbs, CMS pagination and pillar content on page one
- post, index, category, tag and author pages render <SeoBreadcrumbs>
  at the top of their content while journal.breadcrumbs is on (default);
  false leaves the trail to the site's layout, the BreadcrumbList data
  is unchanged
- JournalPagination renders <SeoPagination class-prefix="journal-pagination">
  with the journal's labels: the same class names, plus page 1, the last
  page, gap items, aria labels and head rel prev/next, with links in the
  CMS's pagination form
- with vue-seo-layer's seo.pagination.form 'path' the module registers
  <listing>/<segment>/:page routes (journalPagedRoutes), and the listing
  pages read the page from the :page param or ?page (pageFromRoute)
- the category pillar text renders on page 1 only, the description on
  every page; an h1 in it becomes h2 while the category row's
  content_h1_to_h2 is on (absent counts as on)
2026-11-04 13:54:46 +01:00
Jakub Zych
1010a603f0 feat: body links, outbound rules and image attributes in the post body
- renderJournalBody runs the UI-SPEC L7 order on the sanitized body:
  h1 demotion, the CMS phrase auto-links, the outbound rules and lazy,
  async images, with vue-seo-layer's autolink, applyOutbound and
  imageAttrs; PostBody feeds it the page's resolve answer (autolinks,
  autolink_max, outbound, url) and the seo.links.newTabHint option
- the sanitizer keeps a link rel of only nofollow, sponsored and ugc
  (the Blog Editor's link form, as the Journal plugin's policy does);
  target is never kept, the outbound rules add it
- editor-corpus.json copied again from sm-journal-plugin (link-rel and
  image-decorative entries)
- smoke:ssr script; expectations for auto-links, both new-tab modes and
  the hint switched off
2026-11-04 13:49:36 +01:00
Jakub Zych
289d031201 feat!: render the post page on the shared SEO layer with one H1 in both Journal H1 modes (tracer)
- extend vue-seo-layer (link dependency during development) and drop
  @nuxtjs/seo: the page head, canonical and the one JSON-LD graph come
  from the CMS resolve answer; the post's own title, excerpt and image
  apply only while there is no CMS answer
- remove the journal sitemap source and the copied shared purge route,
  library and plugin (the SEO layer serves POST /api/__site__/cache/purge);
  the deprecated POST /api/__journal__/cache/purge keeps working
- post title is h1 while the Journal API reports content_h1_to_h2 on
  (or absent) and p.journal-post__title while off; journal.postTitleTag
  ('auto' | 'h1' | 'p') overrides it; body h1 headings are demoted to h2
  while the setting is on (gateJournalHtml/sanitizeJournalHtml demoteH1)
- journal.breadcrumbs option and post-title CSS tokens
- smoke harness: count=, header=, noheader= and resolves lines; the
  fixture API also answers the SEO resolve and site endpoints

BREAKING CHANGE: the layer no longer installs @nuxtjs/seo (no
nuxt-site-config, nuxt-schema-org, robots or sitemap module, no
/api/__sitemap__/journal source, no useSiteConfig()); apps take their
head, sitemaps and robots from the SEO plugin through vue-seo-layer.
2026-11-04 13:37:11 +01:00
Jakub Zych
1dd3176060 fix: link post authors by slug, not by login 2026-10-31 13:13:38 +01:00
Jakub Zych
102da43f76 feat: opt-in Journal search page and JournalSearchBox
- journal.routes.search (off by default) registers journal-search, the
  results page for ?q= in the API's relevance order (no sort is sent),
  server-rendered with a Polish-aware result count.
- JournalSearchBox: an icon button and a WAI-ARIA combobox with debounced
  ?fields=suggest suggestions, stale answers dropped, query words marked
  ignoring diacritics; a plain GET form without JS.
- The module keeps the search path out of the route cache and the sitemap
  and marks it noindex, follow.
- Client: suggestPosts and the fields option; useJournalLinks.searchLink.
2026-10-30 18:14:05 +01:00
0a01bfeae4 feat: browsers revalidate route-cached pages; document the site cache purge
- privateRevalidate sets max-age=0 on cache-control values with s-maxage
- server/plugins/journal-revalidate.ts wraps res.setHeader per request, so
  Nitro's 200 and h3's 304 answers both carry it
- purge-check: max-age=0 on 200 and 304, purged _payload.json, a second
  purge after re-caching, X-Real-IP refused, 404 on a server without a token
- README: cachePurgeToken row and a Site cache purge section
2026-10-10 18:27:12 +02:00
a03e076c62 feat: token-protected site cache purge route
- POST /api/__journal__/cache/purge removes every Nitro route-cache entry
- 404 without a 32-character token or behind a proxy, 401 on a wrong bearer
  token (constant-time over sha256 digests)
- routeRules keep /api/__journal__/** out of the route cache
- runtimeConfig.journal.cachePurgeToken (NUXT_JOURNAL_CACHE_PURGE_TOKEN)
- fixture API reload(); testing/purge-check.mjs proves a cached page shows
  a change after a purge; the playground caches with swr 300
2026-10-10 18:19:58 +02:00
a6ee443885 feat: keep Blog Editor markup in the sanitizer and expand Journal embed placeholders into privacy-enhanced iframes
- htmlGate.ts mirrors the four editor-output rules of the Go policy:
  figure data-embed (^youtube$) and data-id (^[A-Za-z0-9_-]{11}$),
  img alt with any text, ol start (integer)
- embeds.ts: EMBED_PROVIDERS (youtube on youtube-nocookie.com),
  EMBED_PLACEHOLDER and expandEmbeds
- JournalPostBody and JournalCategoryHeader render
  expandEmbeds(sanitizeJournalHtml(html)); payload transforms only sanitize
- .journal-embed styles (16 / 9 block, borderless iframe)
- tests: byte copy of the editor corpus, mirror and pipeline cases;
  acme hello-acme carries a placeholder and the SSR smoke checks the iframe
- README Embeds section and decision note Policy amendment
2026-10-10 12:19:00 +02:00
5beb1c5634 fix: sanitize journal HTML instead of blanking it on a regex match
The three-regex gate could be bypassed (an attribute without a space,
<svg/onload>, entity-encoded or tab-split javascript: URLs, MathML
xlink:href, base, meta refresh, forms) and it blanked a whole post or
category when ordinary text such as "Data: 12 maja 2024" matched.

- sanitizeJournalHtml runs sanitize-html with the Journal plugin's
  bluemonday policy: UGC elements plus footer, class, schema.org
  microdata, four validated inline styles, footnote roles, dir=auto,
  two chat transcript data attributes; http, https, mailto and
  relative URLs only
- gateJournalHtml and isSafeJournalHtml keep their signatures; html is
  always the sanitized value and is never blanked
- post body, category content and both page payloads are sanitized;
  the blocked notice, its style and string, and the blocked props and
  content_blocked field are removed
- toPostView moves to postView.ts so list pages and cards do not load
  the sanitizer
- tests cover every bypass from the review, the Data:/metadata: text
  and an imported author footer; the SSR smoke expects the unsafe
  sample's safe paragraphs
2026-10-10 00:01:01 +02:00
783426c5d8 feat: Journal author and tag pages, SEO, sitemap source and RSS route
- author pages resolve slug or login and canonicalise to the slug
- tag pages, per-page SEO meta and canonicals that keep ?page=N
- sitemap source pages the API and builds locs from journal.routes
- /rss.xml proxies the API feed with x-forwarded-for
- neutral README; version 0.1.0
2026-10-09 18:06:20 +02:00
c408eeb2ca feat: Journal list, category pages, pagination, tokens and pl/en strings
- index and category pages paginate with ?page=N and 404 out of range
- category content renders above the list through the html gate
- related posts fall back to the latest posts
- --journal-* tokens and neutral base styles
- pl/en strings registered only for locales the app enables
2026-10-09 18:02:48 +02:00
6f6792f51c feat: scaffold Journal layer with configurable post route and git-install check
- journal module registers the Journal pages at journal.routes paths
- typed anonymous-GET client, html gate, SSR x-forwarded-for forwarding
- fixture API, built-server SSR smoke and git-tag install check
2026-10-09 17:31:45 +02:00