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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Four settings sit beside the skin:
| `board.accent` | One hex colour. Links, buttons, focus rings and highlights follow it. |
| `board.theme` | What a reader who has never touched the theme toggle sees: `system`, `light` or `dark`. |
| `board.logoUrl` | Your own artwork in the header, replacing the generated letter mark. |
| `board.logoHref` | Where that logo points. `/` is the board; an absolute URL is for a board that is one room in a larger site. |
| `board.faviconUrl` | Your own browser-tab icon. |

Two things about the accent are worth knowing, because both are the difference
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export async function render(
unread,
stylesheetUrl: stylesheetUrl(skin, brandOf(settings as Record<string, unknown>)),
logoUrl: urlSetting(settings as Record<string, unknown>, 'board.logoUrl'),
logoHref: urlSetting(settings as Record<string, unknown>, 'board.logoHref'),
faviconUrl: urlSetting(settings as Record<string, unknown>, 'board.faviconUrl'),
themeColor: SKIN_THEME_COLOR[skin],
canonical: options.canonical ?? new URL(url.pathname, services.baseUrl).toString(),
Expand Down
11 changes: 10 additions & 1 deletion apps/server/src/routes/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,14 @@ export function adminRoutes(services: Services) {
{ title: 'Identity', keys: ['board.name', 'board.tagline', 'board.description'] },
{
title: 'Appearance',
keys: ['board.skin', 'board.theme', 'board.accent', 'board.logoUrl', 'board.faviconUrl'],
keys: [
'board.skin',
'board.theme',
'board.accent',
'board.logoUrl',
'board.logoHref',
'board.faviconUrl',
],
},
{ title: 'Registration', keys: ['registration.mode', 'registration.minUsernameLength', 'registration.maxUsernameLength'] },
{ title: 'Posting', keys: ['posts.defaultFormat', 'posts.perPage', 'posts.minLength', 'posts.maxLength', 'posts.editWindowMinutes', 'posts.floodSeconds', 'topics.perPage', 'topics.titleMaxLength'] },
Expand All @@ -183,6 +190,8 @@ export function adminRoutes(services: Services) {
'One hex colour, like #5fff87. Links, buttons and highlights follow it, and it is darkened for the light theme and brightened for the dark one so it stays legible in both. Empty means the built-in palette.',
'board.logoUrl':
'A URL to your own artwork. It replaces the generated letter mark and the board name in the header, so use a wordmark rather than a bare icon.',
'board.logoHref':
'Where the header logo points. / is this board. An absolute URL is for a board that is one room in a larger site — the nav still leads back to the front page, so nobody is stranded.',
'board.faviconUrl': 'A URL to a browser-tab icon. Replaces the bundled tsbb icons.',
'signatures.minPosts':
'How many posts before a signature is shown. A new account with a link-filled signature is the shape of every piece of forum spam, so this is 10 by default.',
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export const DEFAULT_SETTINGS = {
'board.accent': '',
/** Artwork for the header, replacing the generated letter mark. */
'board.logoUrl': '',
/**
* Where the header brand points. '/' is the board, which is what almost
* every board wants; an absolute URL is for a board that is one room in a
* larger site and whose logo should lead back to it. The nav's first item is
* the board's own front page either way, so pointing the logo outward never
* leaves a reader with no way back.
*/
'board.logoHref': '/',
/** A browser-tab icon, replacing the bundled ones. */
'board.faviconUrl': '',

Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/components/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface LayoutProps {
logoUrl?: string;
/** `board.faviconUrl`. Replaces the bundled icons when set. */
faviconUrl?: string;
/** `board.logoHref`. Where the header brand points. Defaults to the board. */
logoHref?: string;
/** The browser-chrome colour for the active skin, light and dark. */
themeColor?: { light: string; dark: string };
canonical?: string;
Expand Down Expand Up @@ -104,7 +106,7 @@ export function Layout(props: LayoutProps) {
<div class="shell">
<header class="site-header">
<div class="container site-header-inner">
<a class="brand" href="/">
<a class="brand" href="${props.logoHref ?? '/'}">
${props.logoUrl
? html`<img class="brand-logo" src="${props.logoUrl}" alt="${props.boardName}" />`
: html`<span class="brand-mark" aria-hidden="true">${props.boardName.slice(0, 1).toUpperCase()}</span>
Expand Down
14 changes: 14 additions & 0 deletions test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,20 @@ describe('skins', () => {
assert.ok(plain.includes('brand-mark'), 'and comes back when the logo is cleared');
});

it('points the header brand wherever board.logoHref says', async () => {
const board = await (await get('/', false)).text();
assert.ok(board.includes('<a class="brand" href="/">'), 'the board itself by default');

await core.setSettings({ 'board.logoHref': 'https://hqtui.com' });
const away = await (await get('/', false)).text();
assert.ok(away.includes('<a class="brand" href="https://hqtui.com">'));
// Pointing the logo outward must not strand a reader: the first nav item is
// still the board's own front page.
assert.ok(away.includes('href="/"'), 'the nav still leads home');

await core.setSettings({ 'board.logoHref': '/' });
});

it('applies board.theme to a reader with no cookie, and never over one with', async () => {
await core.setSettings({ 'board.theme': 'dark' });
const html = await (await get('/', false)).text();
Expand Down
Loading