Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ npm run check

## Contributing

When enabled, **Edit this page** opens a full-page Milkdown editor. Readers can
**Edit this page** opens a full-page Milkdown editor. Readers can
send changes for review without a GitHub account. **Describe a problem** accepts reports.
GitHub editing remains available for experienced contributors. See
[CONTRIBUTING.md](CONTRIBUTING.md).

The account-free service requires separate Cloudflare and GitHub App setup;
see [setup and operations](services/suggestions/README.md). It stays disabled
until configured. Run `npm run test:suggestions` for delivery and source-mapping
see [setup and operations](services/suggestions/README.md). Local editor routes stay disabled until configured; unconfigured mirrors link to
the canonical GitHub Pages editor. Versioned manuals retain their online correction link. Run `npm run test:suggestions` for delivery and source-mapping
tests, `npm run test:editor` for corpus and browser checks, and `npm run suggestions:check` to verify the Worker bundle.

## Manuals shipped with Vizard
Expand Down
6 changes: 3 additions & 3 deletions src/components/EditPageLink.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
import { enabled } from '../lib/suggestions/config.mjs';
import { contributionEnabled, editorUrl } from '../lib/suggestions/config.mjs';
const path = String(Astro.locals.starlightRoute.editUrl ?? '').split('/edit/main/src/content/docs/')[1];
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
const href = path ? `${base}/edit/${path.replace(/\.md$/, '')}/` : '';
const href = path ? editorUrl(path, base) : '';
---
{enabled && href && <a class="edit-page-link" href={href} data-edit-page-link data-pagefind-ignore>Edit this page <span aria-hidden="true">↗</span></a>}
{contributionEnabled && href && <a class="edit-page-link" href={href} data-edit-page-link data-pagefind-ignore>Edit this page <span aria-hidden="true">↗</span></a>}
<script>
const version = new URLSearchParams(location.search).get('manual')?.slice(0, 80);
if (version) document.querySelectorAll<HTMLAnchorElement>('[data-edit-page-link]').forEach(link => {
Expand Down
2 changes: 2 additions & 0 deletions src/components/ManualFooter.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import SectionLinks from './SectionLinks.astro';
import Footer from '@astrojs/starlight/components/Footer.astro';
import SuggestChange from './SuggestChange.astro';
const version = process.env.VIZARD_DOCS_VERSION;
Expand All @@ -13,6 +14,7 @@ const correctionUrl = route !== undefined ? `https://plmn95.github.io/vizard-doc
<a href="https://plmn95.github.io/vizard-docs/versions/">Documentation versions (online)</a>
</div>
<Footer><slot /></Footer>
<SectionLinks />
<style>
.manual-links { margin-block: 2rem 1rem; font-size: var(--sl-text-sm); color: var(--sl-color-gray-2); }
p { margin-bottom: 0.5rem; }
Expand Down
22 changes: 22 additions & 0 deletions src/components/SectionLinks.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div id="section-link-status" class="sr-only" role="status" aria-live="polite" data-pagefind-ignore></div>
<script>
document.querySelectorAll<HTMLAnchorElement>('.sl-anchor-link').forEach(link => {
const heading = link.previousElementSibling?.textContent?.trim() || 'section';
link.setAttribute('aria-label', `Copy link to ${heading}`);
link.title = 'Copy section link';
link.addEventListener('click', async event => {
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
event.preventDefault();
const status = document.getElementById('section-link-status');
try {
await navigator.clipboard.writeText(link.href);
link.title = 'Link copied';
if (status) status.textContent = `Link to ${heading} copied.`;
} catch {
link.title = 'Right-click to copy link';
if (status) status.textContent = 'Could not copy. Use the link’s context menu to copy its address.';
}
});
});
</script>
<style>.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}</style>
6 changes: 3 additions & 3 deletions src/components/SuggestChange.astro
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
import EditPageLink from './EditPageLink.astro';
import { enabled } from '../lib/suggestions/config.mjs';
import { contributionEnabled, editorUrl } from '../lib/suggestions/config.mjs';
const path = String(Astro.locals.starlightRoute.editUrl ?? '').split('/edit/main/src/content/docs/')[1];
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
---
{enabled && path && <section class="contribute" data-pagefind-ignore>
{contributionEnabled && path && <section class="contribute" data-pagefind-ignore>
<EditPageLink />
<a data-edit-page-link href={`${base}/edit/${path.replace(/\.md$/, '')}/?mode=problem`}>Describe a problem</a>
<a data-edit-page-link href={`${editorUrl(path, base)}?mode=problem`}>Describe a problem</a>
<p>No account needed. Changes are reviewed before publication.</p>
</section>}
<style>.contribute { margin-block:2rem; font-size:.8rem; }.contribute>a {margin-left:1.5rem; color:var(--sl-color-gray-3);}.contribute p {color:var(--sl-color-gray-3);margin-top:.65rem;} @media print {.contribute{display:none;}}</style>
6 changes: 6 additions & 0 deletions src/lib/suggestions/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ export const siteKey = process.env.VIZARD_TURNSTILE_SITE_KEY || '';
export const enabled = Boolean(api && siteKey && !process.env.VIZARD_DOCS_VERSION);
if (api && !/^https:\/\/[^\s]+$/.test(api) && !/^http:\/\/(localhost|127\.0\.0\.1):\d+$/.test(api))
throw new Error('VIZARD_SUGGESTIONS_API must be an HTTPS URL (or localhost for development).');

// Unconfigured mirrors still offer the canonical, account-free editor.
export const contributionEnabled = !process.env.VIZARD_DOCS_VERSION;
export function editorUrl(path, base = '') {
return `${enabled ? base.replace(/\/$/, '') : 'https://plmn95.github.io/vizard-docs'}/edit/${path.replace(/\.md$/, '')}/`;
}
11 changes: 11 additions & 0 deletions src/styles/vizard.css
Original file line number Diff line number Diff line change
Expand Up @@ -537,3 +537,14 @@ mobile-starlight-toc:has(li:only-child > a[href='#_top']) {
transition-duration: 0.01ms !important;
}
}

/* Match wrapper metrics to our heading sizes; Starlight uses them for icon placement. */
.sl-markdown-content .sl-heading-wrapper.level-h2 { font-size: clamp(1.35rem, 2.4vw, 1.65rem); }
.sl-markdown-content .sl-heading-wrapper.level-h3 { font-size: 1.15rem; }
.sl-markdown-content .sl-anchor-link { vertical-align: 0.15em; }
.sl-markdown-content .sl-anchor-icon > svg {
display: inline-block;
height: var(--sl-anchor-icon-size);
vertical-align: baseline;
transform: none;
}
24 changes: 24 additions & 0 deletions tests/docs-controls.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test, expect } from '@playwright/test';

test('page editing is discoverable and section links copy without scrolling', async ({ page }) => {
const errors: string[] = [];
page.on('pageerror', error => errors.push(error.message));
await page.addInitScript(() => Object.defineProperty(navigator, 'clipboard', {value: {writeText: async (text: string) => { (window as any).copiedLink = text; }}}));
await page.goto('./');
await expect(page).toHaveTitle(/Vizard/);
await expect(page.locator('.title-row').getByRole('link', {name: 'Edit this page'})).toBeVisible();
const link = page.getByRole('link', {name: 'Copy link to Start here', exact: true});
await link.scrollIntoViewIfNeeded();
const before = await page.evaluate(() => ({y: scrollY, url: location.href}));
await link.click();
await expect(page.getByRole('status')).toHaveText('Link to Start here copied.');
expect(await page.evaluate(() => ({y: scrollY, url: location.href}))).toEqual(before);
expect(await page.evaluate(() => (window as any).copiedLink)).toContain('#start-here');
await page.screenshot({path: '/tmp/vizard-doc-controls-desktop.png'});
await page.setViewportSize({width: 390, height: 844});
await page.goto('./');
await expect(page.locator('.title-row').getByRole('link', {name: 'Edit this page'})).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
await page.screenshot({path: '/tmp/vizard-doc-controls-mobile.png'});
expect(errors).toEqual([]);
});
Loading