Skip to content

Commit bd723d6

Browse files
Merge pull request #55 from MaddyGuthridge/maddy-fedi-verification
Add support for rel="me" site verification and custom site icons
2 parents 1085c50 + eb5924a commit bd723d6

File tree

13 files changed

+120
-4
lines changed

13 files changed

+120
-4
lines changed

src/components/Favicon.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
<script lang="ts">
2+
import consts from '$lib/consts';
3+
24
type Props = {
35
path?: string;
46
};
57
6-
const { path = '/minifolio.png' }: Props = $props();
8+
const { path }: Props = $props();
9+
10+
const actualPath = path ? `/data/${path}` : consts.APP_ICON_URL;
711
</script>
812

9-
<link rel="icon" href={path} />
13+
<link rel="icon" href={actualPath} />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script lang="ts">
2+
import Markdown from './Markdown.svelte';
3+
4+
type Props = {
5+
code: string;
6+
language: string;
7+
};
8+
9+
const { code, language }: Props = $props();
10+
11+
const ticks = '```';
12+
13+
const source = $derived(`${ticks}${language}\n${code}\n${ticks}`);
14+
</script>
15+
16+
<Markdown {source} />

src/components/markdown/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { default as Markdown } from './Markdown.svelte';
22
export { default as MarkdownEditor } from './Markdown.svelte';
3+
export { default as CodeBlock } from './CodeBlock.svelte';
34

45
import EditableMarkdown from './EditableMarkdown.svelte';
56

src/lib/consts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const APP_NAME = 'Minifolio';
77
/** Link to app's GitHub repo */
88
export const APP_GITHUB = 'https://github.com/MaddyGuthridge/Minifolio';
99

10+
export const APP_ICON_URL = '/minifolio.png';
11+
1012
/** Author info -- `[name, URL]` */
1113
type AuthorInfo = [string, string];
1214

@@ -22,6 +24,7 @@ export const EDIT_COMMIT_HESITATION = 1_000;
2224
export default {
2325
APP_NAME,
2426
APP_GITHUB,
27+
APP_ICON_URL,
2528
APP_AUTHOR,
2629
APP_CONTRIBUTORS,
2730
EDIT_COMMIT_HESITATION,

src/lib/delayedUpdate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default class DelayedUpdater<T> {
1313
/** Value about to be committed (or `Option.none()` if there are no changes to be committed) */
1414
current: Option<T>;
1515

16-
constructor (callback: (value: T) => Promise<void>, hesitation: number) {
16+
constructor (callback: (value: T) => Promise<any>, hesitation: number) {
1717
this.callback = callback;
1818
this.hesitation = hesitation;
1919
this.current = Option.none();

src/lib/server/data/config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFile, writeFile } from 'fs/promises';
2-
import { nullable, object, string, validate, type Infer } from 'superstruct';
2+
import { array, nullable, object, string, validate, type Infer } from 'superstruct';
33
import { getDataDir } from './dataDir';
44
import { version } from '$app/environment';
55
import { unsafeLoadConfig } from './migrations/unsafeLoad';
@@ -11,6 +11,8 @@ const CONFIG_JSON = () => `${getDataDir()}/config.json`;
1111
export const ConfigJsonStruct = object({
1212
/** Filename of icon to use for the site */
1313
siteIcon: nullable(string()),
14+
/** Links to place in `<link rel="me" href="{}">` fields */
15+
relMe: array(string()),
1416
/** Version of server that last accessed the config.json */
1517
version: string(),
1618
});
@@ -55,6 +57,7 @@ export async function setConfig(newConfig: ConfigJson) {
5557
export async function initConfig() {
5658
await setConfig({
5759
siteIcon: null,
60+
relMe: [],
5861
version,
5962
});
6063
}

src/lib/server/data/migrations/v0.6.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ async function updatePublicConfig(dataDir: string) {
7272
const oldConfig: any = await unsafeLoadConfig(dataDir);
7373
await setConfig({
7474
version,
75+
relMe: [],
7576
siteIcon: oldConfig.siteIcon,
7677
});
7778
}

src/routes/[...item]/+page.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
/>
6161
<meta name="theme-color" content={data.item.info.color} />
6262
<Favicon path={data.config.siteIcon ?? undefined} />
63+
{#each data.config.relMe as me}
64+
<link rel="me" href={me} />
65+
{/each}
6366
</svelte:head>
6467

6568
<Navbar

src/routes/admin/+page.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import LogOutAll from './LogOutAll.svelte';
1010
import KeySettings from './KeySettings.svelte';
1111
import Favicon from '$components/Favicon.svelte';
12+
import PublicConfig from './PublicConfig.svelte';
1213
1314
type Props = {
1415
data: import('./$types').PageData;
@@ -38,6 +39,7 @@
3839
<div id="paper-container">
3940
<Paper>
4041
<div id="contents">
42+
<PublicConfig configJson={data.config} imageFiles={data.portfolio.ls} />
4143
<GitSettings {data} />
4244
<KeySettings
4345
publicKey={data.keys.publicKey}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script lang="ts">
2+
import { TextArea } from '$components/base';
3+
import { CodeBlock } from '$components/markdown';
4+
import { FilePicker } from '$components/pickers';
5+
import api from '$endpoints';
6+
import consts from '$lib/consts';
7+
import DelayedUpdater from '$lib/delayedUpdate';
8+
import itemId from '$lib/itemId';
9+
import type { ConfigJson } from '$lib/server/data/config';
10+
import { itemFileUrl } from '$lib/urls';
11+
12+
type Props = {
13+
configJson: ConfigJson;
14+
imageFiles: string[];
15+
};
16+
17+
const { configJson, imageFiles }: Props = $props();
18+
19+
// Yucky work-around to make values update
20+
const config = $state(configJson);
21+
22+
const updater = new DelayedUpdater(
23+
async (value: ConfigJson) => api().config.put(value),
24+
consts.EDIT_COMMIT_HESITATION,
25+
);
26+
</script>
27+
28+
<div>
29+
<h2>Public settings</h2>
30+
<form>
31+
<div>
32+
<label for="site-icon"><h3>Site icon</h3></label>
33+
<FilePicker
34+
bind:selected={config.siteIcon}
35+
files={imageFiles}
36+
onchange={() => updater.update(config)}
37+
/>
38+
{#if config.siteIcon}
39+
<p>
40+
Note: the image may be squashed/stretched by the browser when
41+
displayed in the address bar.
42+
</p>
43+
<div class="site-icon-preview">
44+
<img
45+
src={itemFileUrl(itemId.ROOT, config.siteIcon)}
46+
alt={'Preview of site icon'}
47+
/>
48+
</div>
49+
{/if}
50+
</div>
51+
52+
<label for="rel-me"><h3>Verification links</h3></label>
53+
<TextArea
54+
bind:value={() => config.relMe.join('\n'),
55+
(relMe: string) => (config.relMe = relMe.trim().split('\n'))}
56+
oninput={() => updater.update(config)}
57+
/>
58+
<p>Place each URL on a separate line.</p>
59+
<p>The links will be included as:</p>
60+
<CodeBlock
61+
language="html"
62+
code={config.relMe
63+
.map((me) => `<link rel="me" href="${me}" />`)
64+
.join('\n')}
65+
/>
66+
</form>
67+
</div>
68+
69+
<style>
70+
.site-icon-preview {
71+
aspect-ratio: 1 / 1;
72+
max-width: 30%;
73+
margin: 10px;
74+
}
75+
.site-icon-preview > img {
76+
width: 100%;
77+
height: 100%;
78+
border-radius: 10px;
79+
}
80+
</style>

0 commit comments

Comments
 (0)