Post about new bikes, manually set excerpt
All checks were successful
Build and copy to prod / build-and-copy (push) Successful in 2m10s
All checks were successful
Build and copy to prod / build-and-copy (push) Successful in 2m10s
This commit is contained in:
parent
85042de1dc
commit
aab2e16d37
@ -1,10 +1,13 @@
|
|||||||
module.exports = function (eleventyConfig) {
|
const createExcerpt = (post, limit ) => {
|
||||||
eleventyConfig.addFilter('excerpt', (content, limit = 250) => {
|
const withoutTags = post.content.replace(/(<([^>]+)>)/gi, "");
|
||||||
const withoutTags = content.replace(/(<([^>]+)>)/gi, "");
|
|
||||||
|
|
||||||
if (withoutTags.length > limit) {
|
if (withoutTags.length > limit) {
|
||||||
return withoutTags.slice(0, limit) + " […]";
|
return withoutTags.slice(0, limit) + " […]";
|
||||||
}
|
}
|
||||||
return withoutTags;
|
return withoutTags;
|
||||||
|
}
|
||||||
|
module.exports = function (eleventyConfig) {
|
||||||
|
eleventyConfig.addFilter('excerpt', (post, limit = 250) => {
|
||||||
|
return post.data.excerpt || createExcerpt(post, limit);
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -12,7 +12,7 @@
|
|||||||
<li class="stack-xs">
|
<li class="stack-xs">
|
||||||
<h2><a href="{{ item.url }}">{{ item.data.title | safe }}</a></h2>
|
<h2><a href="{{ item.url }}">{{ item.data.title | safe }}</a></h2>
|
||||||
<time class="block" datetime="{{ item.date | dateToRfc3339 }}">{{ item.date | dateDisplay }}</time>
|
<time class="block" datetime="{{ item.date | dateToRfc3339 }}">{{ item.date | dateDisplay }}</time>
|
||||||
<p class="e-content p-summary">{{ item.content | excerpt }}</p>
|
<p class="e-content p-summary">{{ item | excerpt }}</p>
|
||||||
<a href="{{ item.url }}" class="inline-block">Read more</a>
|
<a href="{{ item.url }}" class="inline-block">Read more</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
52
src/assets/js/dvorak-only.js
Normal file
52
src/assets/js/dvorak-only.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
window.customElements.define('dvorak-only', class extends HTMLElement {
|
||||||
|
isDvorak = false;
|
||||||
|
|
||||||
|
isDvorakSpan = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
setInterval(this.checkDvorak, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDvorak = () => {
|
||||||
|
navigator.keyboard.getLayoutMap().then(layoutMap => {
|
||||||
|
this.isDvorak = layoutMap.get("KeyQ") === "'"
|
||||||
|
&& layoutMap.get("KeyW") === ","
|
||||||
|
&& layoutMap.get("KeyE") === "."
|
||||||
|
&& layoutMap.get("KeyR") === "p"
|
||||||
|
&& layoutMap.get("KeyT") === "y"
|
||||||
|
&& layoutMap.get("KeyY") === "f";
|
||||||
|
|
||||||
|
this.isDvorakSpan.innerText = this.isDvorak ? 'Using dvorak' : 'not using dvorak';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
const shadow = this.attachShadow({ mode: "open" });
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
|
||||||
|
this.inputSlot = document.createElement('slot');
|
||||||
|
this.inputSlot.setAttribute('name', 'input');
|
||||||
|
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.setAttribute('type', 'text');
|
||||||
|
|
||||||
|
this.inputSlot.appendChild(input);
|
||||||
|
|
||||||
|
this.isDvorakSpan = document.createElement('span');
|
||||||
|
this.isDvorakSpan.innerText = this.isDvorak ? 'Using dvorak' : 'not using dvorak';
|
||||||
|
|
||||||
|
wrapper.appendChild(this.isDvorakSpan);
|
||||||
|
wrapper.appendChild(this.inputSlot);
|
||||||
|
|
||||||
|
shadow.appendChild(wrapper);
|
||||||
|
|
||||||
|
this.inputSlot.assignedNodes()[0].addEventListener('keydown', (e) => {
|
||||||
|
if (!this.isDvorak) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Cursed components: a dvorak-only text input"
|
title: "Cursed components: a dvorak-only text input"
|
||||||
date: 2024-04-17T12:31:56.557Z
|
date: 2024-04-19T11:00:00.000Z
|
||||||
tags:
|
tags:
|
||||||
- javascript
|
- javascript
|
||||||
- html
|
- html
|
||||||
@ -12,7 +12,9 @@ This post was inspired by Terence Eden's post about [inputs that don't work with
|
|||||||
So, I present, the Dvorak-only Text Input:
|
So, I present, the Dvorak-only Text Input:
|
||||||
|
|
||||||
**TODO:** Make the input
|
**TODO:** Make the input
|
||||||
<input type="text" />
|
<dvorak-only>
|
||||||
|
<textarea slot="input"></textarea>
|
||||||
|
</dvorak-only>
|
||||||
|
|
||||||
## Detecting keyboard layout
|
## Detecting keyboard layout
|
||||||
|
|
||||||
@ -20,13 +22,19 @@ There's no _actual_ way to detect the keyboard layout as a named value using Jav
|
|||||||
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
const dvorakKeys = [
|
||||||
|
["KeyQ", "'"],
|
||||||
|
["KeyW", ","],
|
||||||
|
["KeyE", "."],
|
||||||
|
["KeyR", "p"],
|
||||||
|
["KeyT", "y"],
|
||||||
|
["KeyY", "f"]
|
||||||
|
];
|
||||||
navigator.keyboard.getLayoutMap().then(layoutMap => {
|
navigator.keyboard.getLayoutMap().then(layoutMap => {
|
||||||
if (layoutMap.get("KeyQ") === "'"
|
const isUsingDvorak = dvorakKeys.every(
|
||||||
&& layoutMap.get("KeyW") === ","
|
([keyCode, expected]) => layoutMap.get(keyCode) === expected
|
||||||
&& layoutMap.get("KeyE") === "."
|
);
|
||||||
&& layoutMap.get("KeyR") === "p"
|
if (isUsingDvorak) {
|
||||||
&& layoutMap.get("KeyT") === "y"
|
|
||||||
&& layoutMap.get("KeyY") === "f") {
|
|
||||||
// The user is (probably) using Dvorak
|
// The user is (probably) using Dvorak
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -36,35 +44,28 @@ Because this call happens inside a Promise, it can't be directly used within an
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let isDvorak = false;
|
let isDvorak = false;
|
||||||
|
const dvorakKeys = [
|
||||||
|
["KeyQ", "'"],
|
||||||
|
["KeyW", ","],
|
||||||
|
["KeyE", "."],
|
||||||
|
["KeyR", "p"],
|
||||||
|
["KeyT", "y"],
|
||||||
|
["KeyY", "f"]
|
||||||
|
];
|
||||||
setInterval(
|
setInterval(
|
||||||
() => navigator.keyboard.getLayoutMap().then(layoutMap => {
|
() => navigator.keyboard.getLayoutMap().then(layoutMap => {
|
||||||
isDvorak = layoutMap.get("KeyQ") === "'"
|
isDvorak = dvorakKeys.every(
|
||||||
&& layoutMap.get("KeyW") === ","
|
([keyCode, expected]) => layoutMap.get(keyCode) === expected
|
||||||
&& layoutMap.get("KeyE") === "."
|
);
|
||||||
&& layoutMap.get("KeyR") === "p"
|
|
||||||
&& layoutMap.get("KeyT") === "y"
|
|
||||||
&& layoutMap.get("KeyY") === "f";
|
|
||||||
}),
|
}),
|
||||||
200
|
200
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Preventing the input
|
||||||
|
|
||||||
|
This part's pretty simple. Basically, just
|
||||||
|
|
||||||
[^1]: But that's pretty on-brand for this particular use-case.
|
[^1]: But that's pretty on-brand for this particular use-case.
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script src="/assets/js/dvorak-only.js" defer></script>
|
||||||
let isDvorak = false;
|
|
||||||
|
|
||||||
setInterval(
|
|
||||||
() => navigator.keyboard.getLayoutMap().then(layoutMap => {
|
|
||||||
isDvorak = layoutMap.get("KeyQ") === "'"
|
|
||||||
&& layoutMap.get("KeyW") === ","
|
|
||||||
&& layoutMap.get("KeyE") === "."
|
|
||||||
&& layoutMap.get("KeyR") === "p"
|
|
||||||
&& layoutMap.get("KeyT") === "y"
|
|
||||||
&& layoutMap.get("KeyY") === "f";
|
|
||||||
}),
|
|
||||||
200
|
|
||||||
);
|
|
||||||
</script>
|
|
@ -1,11 +1,15 @@
|
|||||||
---
|
---
|
||||||
title: "Measuring the right thing"
|
title: An observation on measuring speed vs cadence
|
||||||
description: A brief rumination about choosing the right thing to measure, and what the impact is
|
|
||||||
tags:
|
tags:
|
||||||
- cycling
|
- cycling
|
||||||
- draft
|
date: 2024-04-21T14:30:00.000Z
|
||||||
---
|
---
|
||||||
|
|
||||||
|
I'm writing this quickly after coming back from an 82km bike ride.
|
||||||
|
|
||||||
|
When I first started cycling, I didn't have a dedicated GPS unit, instead I just recorded my rides using my phone so that I could share them to Strava. I couldn't tell how fast I was going until afterwards, so my speed didn't matter; I was just riding for fun.
|
||||||
|
|
||||||
|
|
||||||
* Started cycling, had no GPS unit and just recorded using phone
|
* Started cycling, had no GPS unit and just recorded using phone
|
||||||
* Speed didn't matter
|
* Speed didn't matter
|
||||||
* Had some fast-paced rides last year
|
* Had some fast-paced rides last year
|
||||||
|
31
src/blog/posts/2024/4/new-bike-day.md
Normal file
31
src/blog/posts/2024/4/new-bike-day.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: New bike(s) day
|
||||||
|
date: 2024-04-24T19:11:13Z
|
||||||
|
tags:
|
||||||
|
- cycling
|
||||||
|
excerpt: Because I have a problem, in the last few weeks I've bought not one, but two, new bikes
|
||||||
|
---
|
||||||
|
|
||||||
|
I have _two_ new bikes[^1], so thought I'd post about them[^2].
|
||||||
|
|
||||||
|
## The single-speed
|
||||||
|
|
||||||
|
The first is a single speed beater I picked up from Facebook marketplace for £35 whole pounds. I decided to buy a single speed because my geared road bikes have been costing me too much time & money in maintenance for commuting.
|
||||||
|
|
||||||
|
![A black single speed bike with deep-section rims, leaning up against the back of a car](./src/images/single-speed.jpeg)
|
||||||
|
|
||||||
|
I actually was looking for single speed wheels so that I could convert my old 80's Peugeot, however this was listed for just £5 more than a heavily-used wheelset. So I snapped it up, replaced the saddle, unseized the seat post[^3] and it's good to go.
|
||||||
|
|
||||||
|
It's actually really fun to ride, the gearing is pretty much perfect for my cadence so I'm actually just as quick on it as I was on a road bike.
|
||||||
|
|
||||||
|
## The fancy Ribble
|
||||||
|
|
||||||
|
The second is my fancy new Ribble Endurance SL Disc, which I've been waiting for weeks to get. It's my first carbon bike, and is lightweight and should be really comfortable for longer rides.
|
||||||
|
|
||||||
|
![A red Ribble road bike on a showroom stand. The sign says “Congratulations Lewis! On your Endurance SL Disc"](./src/images/ribble-endurance.jpeg)
|
||||||
|
|
||||||
|
I've not had a chance to go out on it yet, unfortunately, but the next dry day we have I'll be out on it for sure.
|
||||||
|
|
||||||
|
[^1]: Because, of course, the correct number of bikes is `n+1`
|
||||||
|
[^2]: Actually, I've just setup [EchoFeed](https://echofeed.app) and wanted to test it out
|
||||||
|
[^3]: The TL;DR there is penetrating oil, and then using a small screwdriver to widen the seat tube to free the post
|
@ -161,7 +161,6 @@ header {
|
|||||||
p {
|
p {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
max-width: 70ch;
|
max-width: 70ch;
|
||||||
text-wrap: balance;
|
|
||||||
|
|
||||||
&:has(> picture) {
|
&:has(> picture) {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<updated>{{ post.date | dateToRfc3339 }}</updated>
|
<updated>{{ post.date | dateToRfc3339 }}</updated>
|
||||||
<id>{{ absolutePostUrl }}</id>
|
<id>{{ absolutePostUrl }}</id>
|
||||||
<content xml:lang="{{ metadata.site.language }}" type="html">{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
|
<content xml:lang="{{ metadata.site.language }}" type="html">{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
|
||||||
<summary>{{ post.content | excerpt }}</summary>
|
<summary>{{ post | excerpt }}</summary>
|
||||||
{% set filteredTags = post.data.tags | except("posts") | except("draft") %}
|
{% set filteredTags = post.data.tags | except("posts") | except("draft") %}
|
||||||
{% for tag in filteredTags %}
|
{% for tag in filteredTags %}
|
||||||
<category term="{{ tag }}" />
|
<category term="{{ tag }}" />
|
||||||
|
@ -23,7 +23,7 @@ eleventyImport:
|
|||||||
<description>{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</description>
|
<description>{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</description>
|
||||||
<pubDate>{{ post.date | dateToRfc822 }}</pubDate>
|
<pubDate>{{ post.date | dateToRfc822 }}</pubDate>
|
||||||
<dc:creator>{{ metadata.author.name }}</dc:creator>
|
<dc:creator>{{ metadata.author.name }}</dc:creator>
|
||||||
<atom:summary>{{ post.content | excerpt }}</atom:summary>
|
<atom:summary>{{ post | excerpt }}</atom:summary>
|
||||||
<guid>{{ absolutePostUrl }}</guid>
|
<guid>{{ absolutePostUrl }}</guid>
|
||||||
</item>
|
</item>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
@ -31,6 +31,7 @@ eleventyImport:
|
|||||||
<updated>{{ post.date | dateToRfc3339 }}</updated>
|
<updated>{{ post.date | dateToRfc3339 }}</updated>
|
||||||
<id>{{ absolutePostUrl }}</id>
|
<id>{{ absolutePostUrl }}</id>
|
||||||
<content xml:lang="{{ metadata.site.language }}" type="html">{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
|
<content xml:lang="{{ metadata.site.language }}" type="html">{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
|
||||||
|
<summary>{{ post | excerpt }}</summary>
|
||||||
</entry>
|
</entry>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</feed>
|
</feed>
|
@ -26,7 +26,7 @@ eleventyImport:
|
|||||||
<description>{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</description>
|
<description>{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</description>
|
||||||
<pubDate>{{ post.date | dateToRfc822 }}</pubDate>
|
<pubDate>{{ post.date | dateToRfc822 }}</pubDate>
|
||||||
<dc:creator>{{ metadata.author.name }}</dc:creator>
|
<dc:creator>{{ metadata.author.name }}</dc:creator>
|
||||||
<atom:summary>{{ post.content | excerpt }}</atom:summary>
|
<atom:summary>{{ post | excerpt }}</atom:summary>
|
||||||
<guid>{{ absolutePostUrl }}</guid>
|
<guid>{{ absolutePostUrl }}</guid>
|
||||||
</item>
|
</item>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
BIN
src/images/ribble-endurance.jpeg
Normal file
BIN
src/images/ribble-endurance.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
BIN
src/images/single-speed.jpeg
Normal file
BIN
src/images/single-speed.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 MiB |
Loading…
Reference in New Issue
Block a user