blog: vibe check #21
This commit is contained in:
parent
1743c9cfd4
commit
98bc188722
13 changed files with 160 additions and 65 deletions
|
|
@ -32,7 +32,7 @@
|
|||
"svelte-preprocess": "^6.0.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^5.4.11",
|
||||
"vite": "^6.0.7",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
@ -60,6 +60,6 @@
|
|||
"zod": "^3.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.11.1"
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
src/content/blog/2025-01-09-vibe-check-21.md
Normal file
27
src/content/blog/2025-01-09-vibe-check-21.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: 'Vibe Check #21'
|
||||
date: 1970-01-01T00:00:00.000Z
|
||||
slug: 2025-01-09-vibe-check-21
|
||||
author: Thomas Wilson
|
||||
|
||||
---
|
||||
|
||||

|
||||
December comes to us, mild in temperament and temperature this year. Although this season didn't see quite the Grand Social Obligations Tour that previous festive periods have, the household has remained busy (and largely healthy) through December, something to always be celebrated.
|
||||
|
||||
The month contained moments of great personal and social delight which, though both real and invaluable, aren't fit to share far and wide on the open internet. Let the record show that I feel both grateful and fulfilled by the people in my life, and my relationships to them.
|
||||
|
||||
December 2024 saw my last working day at my (now previous) job as a Senior Software Engineer. In a few weeks I'll be starting a more leadership-focused position in a larger organisation, with a much larger technical footprint. I am excited, and glad to have taken a short (three-week) break between positions. Time off has a funny way of being neither as much time, nor off, as one expects at the outset.
|
||||
|
||||
A few months before the end of 2024 I wrote a list of projects I had started, and wanted to finish. These were things I *wanted* to finish but which had started dragging. I am proud to report that in the last four weeks of 2024, I managed to:
|
||||
|
||||
1. Finish knitting my first pair of socks
|
||||
2. Finish the second book in John Gwynne's *Bloodsword Saga*
|
||||
|
||||
I continue to make a lot of my own clothes. My relationship to this practice *is* getting more complicated. I have a lot of thoughts about how inaccessible the knowledge of "proper" tailoring and garment construction is. I wonder if that's a "real" problem, or only a "me" problem. I am infuriated by how often "you just have to know" is the answer to questions, and I wonder how much [Cargo Culting](https://en.wikipedia.org/wiki/Cargo_cult) or Rain Dancing (i.e. rituals seem to have previously worked, but through unclear mechanisms) happens here.
|
||||
|
||||
But I am delighted to report that knitting has little to none of this. My experience has been that people are *delighted* to provide you with very clear, standardised instructions on how to make a pair of socks. Even better, similar services are available for many other garments! I am also currently batting 100% on "unclear instructions that an LLM can help clarify". It definitely takes *longer* to make knitted garments - *weeks* for two socks - and one cannot practically wear only knitted cloths, but it is a skill I am grateful to have, and to improve.
|
||||
|
||||
I wanted the pressure to finish the garment precisely for this reason: it is refreshing and I am glad to have done it, but I found it hard to assign time to it.
|
||||
|
||||
The book was a similar story: a recently-completed trilogy that I am actively enjoying. But at 600-some pages, and as a physical book (I am 1:2 on books:audiobooks) I found myself picking up, and finishing, books around it. By setting the end-of-year marker, I was able to leave that feeling of drag behind. And it feels fantastic.
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { resolve } from 'path';
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { BlogController } from '../../../../lib/blog/BlogController.js';
|
||||
import { dump as dumpYaml } from 'js-yaml';
|
||||
|
||||
import { BlogController } from '../../../../lib/blog/BlogController.js';
|
||||
|
||||
const thisDirectory = import.meta.url
|
||||
.replace('file://', '')
|
||||
.split('/')
|
||||
|
|
@ -11,6 +12,7 @@ const thisDirectory = import.meta.url
|
|||
.join('/');
|
||||
|
||||
export const POST: RequestHandler = async ({ getClientAddress, request }) => {
|
||||
console.log(`Received request to create new blog post.`);
|
||||
const address = await getClientAddress();
|
||||
let fileName: string;
|
||||
let markdownContent: string;
|
||||
|
|
@ -33,9 +35,18 @@ export const POST: RequestHandler = async ({ getClientAddress, request }) => {
|
|||
error(400, 'Error in request body.');
|
||||
}
|
||||
|
||||
console.log({
|
||||
fileName,
|
||||
markdownContent,
|
||||
title,
|
||||
date,
|
||||
slug,
|
||||
author,
|
||||
});
|
||||
|
||||
if ([fileName, markdownContent, title, date, slug, author].includes(undefined)) {
|
||||
error(400, `Missing parameters.`);
|
||||
} else if (!['127.0.0.1', '::1'].includes(address) ) {
|
||||
} else if (!['127.0.0.1', '::1'].includes(address)) {
|
||||
console.log(address);
|
||||
error(403, `Forbidden.`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,66 @@
|
|||
import { BlogController } from '$lib/blog/BlogController.js';
|
||||
import type { Actions } from '@sveltejs/kit';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import { dump as dumpYaml } from 'js-yaml';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const thisDirectory = import.meta.url
|
||||
.replace('file://', '')
|
||||
.split('/')
|
||||
.filter((part) => part !== '+server.ts')
|
||||
.join('/');
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const actions = {
|
||||
default: async ({ getClientAddress, request }) => {
|
||||
console.log(`Received request to create new blog post.`);
|
||||
const address = await getClientAddress();
|
||||
let markdownContent: string;
|
||||
let title: string;
|
||||
let date: string;
|
||||
let slug: string;
|
||||
let author: string;
|
||||
|
||||
try {
|
||||
const requestBody = await request.formData();
|
||||
markdownContent = requestBody.get('content') as string;
|
||||
title = requestBody.get('title') as string;
|
||||
date = requestBody.get('date') as string;
|
||||
slug = requestBody.get('slug') as string;
|
||||
author = requestBody.get('author') as string;
|
||||
} catch (e: any) {
|
||||
console.log(`Caught error destructuring request body`);
|
||||
console.error(e);
|
||||
error(400, 'Error in request body.');
|
||||
}
|
||||
|
||||
if ([markdownContent, title, date, slug, author].includes(undefined)) {
|
||||
error(400, `Missing parameters.`);
|
||||
} else if (!['127.0.0.1', '::1'].includes(address)) {
|
||||
console.log(address);
|
||||
error(403, `Forbidden.`);
|
||||
}
|
||||
|
||||
const controller = await BlogController.singleton();
|
||||
|
||||
const worryinglyManualFrontMatter = [
|
||||
`---`,
|
||||
dumpYaml({ title, date: new Date(date), slug, author }),
|
||||
`---`,
|
||||
].join(`\n`);
|
||||
|
||||
const escapedMarkdown = markdownContent.replaceAll(/\\n/g, '\n');
|
||||
|
||||
const contentWithFrontmatter = [worryinglyManualFrontMatter, escapedMarkdown].join(`\n`);
|
||||
|
||||
const resolvedFileName = resolve(thisDirectory, `../../../content/blog/${slug}.md`);
|
||||
|
||||
console.log({ resolvedFileName });
|
||||
console.log(`\n${contentWithFrontmatter}\n`);
|
||||
|
||||
await controller.createBlogPost(resolvedFileName, contentWithFrontmatter);
|
||||
|
||||
redirect(307, `/blog/${slug}`);
|
||||
},
|
||||
} satisfies Actions;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
text-align: center;
|
||||
line-height: 125%;
|
||||
max-width: 30ch;
|
||||
font-family: var(--font-family-serif);
|
||||
}
|
||||
|
||||
.post-author {
|
||||
|
|
@ -93,6 +94,7 @@
|
|||
text-align: center;
|
||||
line-height: 100%;
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.post-date {
|
||||
|
|
@ -129,22 +131,22 @@
|
|||
|
||||
#article {
|
||||
--padding: 4px;
|
||||
background-color: var(--colour-scheme-background-accent);
|
||||
--font-size: var(--font-size-base);
|
||||
padding: var(--padding);
|
||||
font-size: var(--font-size-base);
|
||||
font-size: var(--font-size);
|
||||
width: var(--width);
|
||||
max-width: var(--max-width);
|
||||
flex-grow: 1;
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
--font-size: 1rem;
|
||||
--font-size: var(--font-size-sm);
|
||||
--padding: 4px;
|
||||
letter-spacing: normal;
|
||||
line-height: 110%;
|
||||
--padding: 4px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 700px) {
|
||||
--font-size: 1.29rem;
|
||||
--font-size: var(--font-size-base);
|
||||
--padding: 1.5rem 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { format as formatDate } from "date-fns";
|
||||
import { BlogPost } from "$lib/blog/BlogPost.js";
|
||||
import { goto } from "$app/navigation";
|
||||
let title = $state("");
|
||||
let author = $state("Thomas Wilson");
|
||||
let date = new Date();
|
||||
|
|
@ -24,42 +21,18 @@
|
|||
const slugifiedTitle = slugifyString(title);
|
||||
slug = `${dateAsString}-${slugifiedTitle}`;
|
||||
}
|
||||
|
||||
async function onCreate() {
|
||||
const requestBody = {
|
||||
title,
|
||||
author,
|
||||
slug,
|
||||
markdownContent: content,
|
||||
fileName: `${slug}.md`,
|
||||
date: date.toISOString(),
|
||||
};
|
||||
|
||||
fetch("/api/blog/new.json", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
}).then(async (res) => {
|
||||
if (res.status === 200) {
|
||||
await goto(`/blog/${slug}`);
|
||||
} else {
|
||||
alert("Something went wrong");
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="new-blog-post">
|
||||
<a href="/blog">Back to Blog</a>
|
||||
<h1>New Blog Post</h1>
|
||||
<form onsubmit={preventDefault(onCreate)}>
|
||||
<form method="POST" action="/blog">
|
||||
<div class="field">
|
||||
<label class="field__label" for="title">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
name="title"
|
||||
required
|
||||
bind:value={title}
|
||||
onchange={handleTitleChange}
|
||||
|
|
@ -67,21 +40,33 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="author">Author</label>
|
||||
<input type="text" id="author" required bind:value={author} />
|
||||
<input
|
||||
type="text"
|
||||
name="author"
|
||||
id="author"
|
||||
required
|
||||
bind:value={author}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="slug">Slug</label>
|
||||
<input type="text" id="slug" required bind:value={slug} />
|
||||
<input type="text" name="slug" id="slug" required bind:value={slug} />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="content">Content</label>
|
||||
<textarea id="content" rows="10" cols="50" bind:value={content}></textarea>
|
||||
<textarea
|
||||
name="content"
|
||||
id="content"
|
||||
rows="10"
|
||||
cols="50"
|
||||
bind:value={content}
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="submit">
|
||||
<button class="create-button">Publish</button>
|
||||
<button type="submit" class="create-button">Publish</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@
|
|||
I craft well-meaning things, like software, clothes, photographs, and words.
|
||||
</p>
|
||||
<p class="body strapline">
|
||||
I am trying to make things stronger which don't rely on exploitation,
|
||||
excessive waste, or dishonesty; and to try and build a kinder, fairer world
|
||||
even when it's hard.
|
||||
I want to help build things that don't rely on exploitation, excessive
|
||||
waste, or dishonesty; and to try and build a kinder, fairer world even when
|
||||
it's hard.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
totalNumberOfGuesses: 0,
|
||||
guesses: [],
|
||||
correctDays: [],
|
||||
incorrectDays: []
|
||||
incorrectDays: [],
|
||||
});
|
||||
|
||||
interface Props {
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
totalNumberOfGuesses: 0,
|
||||
guesses: [],
|
||||
correctDays: [],
|
||||
incorrectDays: []
|
||||
incorrectDays: [],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -69,13 +69,13 @@
|
|||
revealNotification(true);
|
||||
$guessingHistory.correctDays = [
|
||||
...$guessingHistory.correctDays,
|
||||
todaysDateString
|
||||
todaysDateString,
|
||||
];
|
||||
} else {
|
||||
revealNotification(false);
|
||||
$guessingHistory.incorrectDays = [
|
||||
...$guessingHistory.incorrectDays,
|
||||
todaysDateString
|
||||
todaysDateString,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +152,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss" type="text/postcss">
|
||||
<style>
|
||||
:root {
|
||||
--background-colour: hsl(40, 8%, 85%);
|
||||
--colour-light-grey: hsl(28, 35%, 85%);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
const resultSpring = spring(20, {
|
||||
stiffness: 0.1,
|
||||
damping: 0.15,
|
||||
precision: 0.01
|
||||
precision: 0.01,
|
||||
});
|
||||
|
||||
function triggerAnimation() {
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
{/if}
|
||||
</section>
|
||||
|
||||
<style type="text/postcss">
|
||||
<style>
|
||||
.notification {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
{/if}
|
||||
</section>
|
||||
|
||||
<style type="text/postcss">
|
||||
<style>
|
||||
.options {
|
||||
padding: 12px;
|
||||
display: grid;
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@
|
|||
doesUserHaveGuessingHistory,
|
||||
correctGuessDays,
|
||||
incorrectGuessDays,
|
||||
currentStreakLength
|
||||
currentStreakLength,
|
||||
}: Props = $props();
|
||||
|
||||
const todayAsString = formatDate(new Date(), "yyyy-MM-dd");
|
||||
const calculator = new SunriseSunsetStreakCalculator(todayAsString);
|
||||
let hasTextBeenCopied = $state(false);
|
||||
|
||||
let historyStatement = $derived(calculator.getShareableStatement(
|
||||
let historyStatement = $derived(
|
||||
calculator.getShareableStatement(
|
||||
correctGuessDays,
|
||||
incorrectGuessDays,
|
||||
new Date()
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
function copyHistory() {
|
||||
if (browser) {
|
||||
|
|
@ -52,7 +54,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<style type="text/postcss">
|
||||
<style>
|
||||
.score {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
|
|
@ -71,7 +73,9 @@
|
|||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 0, 0, 0.1),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.score__title {
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@
|
|||
--line-height-md: 140%;
|
||||
--line-height-lg: 145%;
|
||||
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-xs: 11px;
|
||||
--font-size-sm: 13px;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.25rem;
|
||||
--font-size-xl: 1.5rem;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ const config = {
|
|||
extensions: ['.svelte', '.md'],
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: [preprocess(), mdsvex({
|
||||
preprocess: [preprocess(),
|
||||
mdsvex({
|
||||
extensions: ['.md'],
|
||||
})],
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue