A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

Add a shared "Powered by SkyPress" footer to reading pages

Single posts ended with a bare "More from {publication}" line, and
publication index pages had no footer at all — they surfaced the RSS
feed as a small link under the title instead. Neither page attributed
the platform.

Introduce PublicationFooter.astro, shared by both reading pages: the
publication name plus its RSS feed on the left, a quiet "Powered by
SkyPress" link home on the right. It takes its data as props and pulls
in none of the browser-only editor packages, so it stays on the safe
read path.

On the publication page this replaces the under-title RSS link; on the
single post it replaces the old reader__foot. The RSS glyph is the one
already used on the publication page, so the icon stays consistent.

+123 -38
+79
src/components/PublicationFooter.astro
··· 1 + --- 2 + /** 3 + * Shared reading-page footer — used on single posts and on the publication 4 + * index so both end the same way: the publication name + its RSS feed on the 5 + * left, a quiet "Powered by SkyPress" attribution on the right. 6 + * 7 + * Read-path component: data is passed in as props, and it pulls in none of the 8 + * browser-only editor packages (see AGENTS.md). 9 + */ 10 + interface Props { 11 + /** Publication name, shown as the left-hand link. */ 12 + name: string; 13 + /** Link to the publication page (e.g. `/@handle/slug`). */ 14 + pubUrl: string; 15 + /** That publication's RSS feed URL. */ 16 + feedHref: string; 17 + } 18 + 19 + const { name, pubUrl, feedHref } = Astro.props; 20 + --- 21 + 22 + <footer class="pub-footer"> 23 + <span class="pub-footer__left"> 24 + <a class="pub-footer__pub" href={pubUrl}>{name}</a> 25 + <a class="pub-footer__feed" href={feedHref} aria-label={`${ name } RSS feed`}> 26 + <svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"> 27 + <circle cx="4.5" cy="19.5" r="2.5" /> 28 + <path d="M2 10.5v3.2a7.3 7.3 0 0 1 7.3 7.3h3.2A10.5 10.5 0 0 0 2 10.5Z" /> 29 + <path d="M2 3.6v3.2A14.2 14.2 0 0 1 16.2 21h3.2A17.4 17.4 0 0 0 2 3.6Z" /> 30 + </svg> 31 + </a> 32 + </span> 33 + <a class="pub-footer__powered" href="/">Powered by SkyPress</a> 34 + </footer> 35 + 36 + <style> 37 + .pub-footer { 38 + display: flex; 39 + align-items: center; 40 + justify-content: space-between; 41 + flex-wrap: wrap; 42 + gap: 0.5rem 1.5rem; 43 + margin-top: 3.5rem; 44 + padding-top: 1.5rem; 45 + border-top: 1px solid var(--line); 46 + } 47 + .pub-footer__left { 48 + display: inline-flex; 49 + align-items: center; 50 + gap: 0.5rem; 51 + } 52 + .pub-footer__pub { 53 + color: var(--sun); 54 + text-decoration: none; 55 + } 56 + .pub-footer__pub:hover { 57 + text-decoration: underline; 58 + text-underline-offset: 0.2em; 59 + } 60 + .pub-footer__feed { 61 + display: inline-flex; 62 + align-items: center; 63 + color: var(--muted); 64 + transition: color 0.12s ease; 65 + } 66 + .pub-footer__feed:hover { 67 + color: var(--sun); 68 + } 69 + .pub-footer__powered { 70 + color: var(--muted); 71 + font-family: var(--font-mono); 72 + font-size: 0.72rem; 73 + letter-spacing: 0.04em; 74 + text-decoration: none; 75 + } 76 + .pub-footer__powered:hover { 77 + color: var(--sun); 78 + } 79 + </style>
+39
src/components/PublicationFooter.meta.test.ts
··· 1 + /** 2 + * Source-level guard for the shared publication footer. Page/component rendering 3 + * through astro/container isn't viable in this jsdom-pinned suite (see 4 + * Base.meta.test.ts / ErrorScene.meta.test.ts), so we pin the wiring at the 5 + * source level. 6 + */ 7 + import { readFileSync } from 'node:fs'; 8 + import { dirname, join } from 'node:path'; 9 + import { fileURLToPath } from 'node:url'; 10 + import { describe, expect, it } from 'vitest'; 11 + 12 + const here = dirname( fileURLToPath( import.meta.url ) ); 13 + const component = readFileSync( join( here, './PublicationFooter.astro' ), 'utf8' ); 14 + 15 + describe( 'PublicationFooter component', () => { 16 + it( 'declares the name, pubUrl and feedHref props', () => { 17 + expect( component ).toMatch( /name/ ); 18 + expect( component ).toMatch( /pubUrl/ ); 19 + expect( component ).toMatch( /feedHref/ ); 20 + } ); 21 + 22 + it( 'links the publication name to the pubUrl prop', () => { 23 + expect( component ).toMatch( /href=\{\s*pubUrl\s*\}[^>]*>\s*\{\s*name\s*\}/ ); 24 + } ); 25 + 26 + it( 'links the RSS icon to the feedHref prop with an accessible label', () => { 27 + expect( component ).toMatch( /href=\{\s*feedHref\s*\}/ ); 28 + expect( component ).toMatch( /aria-label=/ ); 29 + expect( component ).toMatch( /<svg/ ); 30 + } ); 31 + 32 + it( 'links "Powered by SkyPress" to the site root', () => { 33 + expect( component ).toMatch( /href="\/"[^>]*>\s*Powered by SkyPress/ ); 34 + } ); 35 + 36 + it( 'is dependency-free (no @wordpress imports on the read path)', () => { 37 + expect( component ).not.toMatch( /@wordpress\// ); 38 + } ); 39 + } );
+2 -8
src/pages/[author]/[slug]/[rkey].astro
··· 1 1 --- 2 2 import Base from '../../../layouts/Base.astro'; 3 3 import Logo from '../../../components/Logo.astro'; 4 + import PublicationFooter from '../../../components/PublicationFooter.astro'; 4 5 import { resolveAuthor } from '../../../lib/reader/identity'; 5 6 import { getRecord } from '../../../lib/reader/records'; 6 7 import { resolveReaderPublication } from '../../../lib/reader/publications'; ··· 158 159 </p> 159 160 <h1 class="reader__title">{title}</h1> 160 161 <article class="reader__article" set:html={html} /> 161 - <footer class="reader__foot"> 162 - <a class="reader__author" href={pubUrl}>More from {publication!.name}</a> 163 - </footer> 162 + <PublicationFooter name={publication!.name} pubUrl={pubUrl} feedHref={feedHref} /> 164 163 </main> 165 164 </Base> 166 165 ) ··· 240 239 } 241 240 .reader__article :global(code) { 242 241 font-family: var(--font-mono); 243 - } 244 - .reader__foot { 245 - margin-top: 3.5rem; 246 - padding-top: 1.5rem; 247 - border-top: 1px solid var(--line); 248 242 } 249 243 </style>
+3 -30
src/pages/[author]/[slug]/index.astro
··· 1 1 --- 2 2 import Base from '../../../layouts/Base.astro'; 3 3 import Logo from '../../../components/Logo.astro'; 4 + import PublicationFooter from '../../../components/PublicationFooter.astro'; 4 5 import { resolveAuthor } from '../../../lib/reader/identity'; 5 6 import { listRecords } from '../../../lib/reader/records'; 6 7 import { resolveReaderPublication } from '../../../lib/reader/publications'; ··· 128 129 </a> 129 130 </p> 130 131 {publication!.description && <p class="pub__lede">{publication!.description}</p>} 131 - <p class="pub__feed"> 132 - <a href={feedHref}> 133 - <svg class="pub__feedicon" width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"> 134 - <circle cx="4.5" cy="19.5" r="2.5" /> 135 - <path d="M2 10.5v3.2a7.3 7.3 0 0 1 7.3 7.3h3.2A10.5 10.5 0 0 0 2 10.5Z" /> 136 - <path d="M2 3.6v3.2A14.2 14.2 0 0 1 16.2 21h3.2A17.4 17.4 0 0 0 2 3.6Z" /> 137 - </svg> 138 - RSS 139 - </a> 140 - </p> 141 132 </div> 142 133 </div> 143 134 ··· 154 145 ) )} 155 146 </ul> 156 147 )} 148 + 149 + <PublicationFooter name={publication!.name} pubUrl={`/${ author }/${ slug }`} feedHref={feedHref} /> 157 150 </main> 158 151 </Base> 159 152 ) ··· 237 230 font-size: 1.15rem; 238 231 max-width: 42ch; 239 232 margin: 1rem auto 0; 240 - } 241 - .pub__feed { 242 - margin: 1.1rem 0 0; 243 - } 244 - .pub__feed a { 245 - display: inline-flex; 246 - align-items: center; 247 - gap: 0.35rem; 248 - font-family: var(--font-mono); 249 - font-size: 0.72rem; 250 - letter-spacing: 0.04em; 251 - text-transform: uppercase; 252 - color: var(--muted); 253 - text-decoration: none; 254 - } 255 - .pub__feed a:hover { 256 - color: var(--sun); 257 - } 258 - .pub__feedicon { 259 - color: var(--sun); 260 233 } 261 234 .pub__empty { 262 235 color: var(--muted);