rework sidenotes to make nesting possible
This commit is contained in:
parent
716792e8a6
commit
9eeb3e87bd
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,4 +8,5 @@ node_modules
|
|||||||
!.env.example
|
!.env.example
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
**/_test.*
|
**/_test.*
|
||||||
|
/scratch
|
@ -8,31 +8,36 @@
|
|||||||
counter-reset: sidenote;
|
counter-reset: sidenote;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter {
|
.counter.anchor {
|
||||||
counter-increment: sidenote;
|
|
||||||
color: #444;
|
color: #444;
|
||||||
margin-left: 0.065rem;
|
margin-left: 0.065rem;
|
||||||
|
font-size: 0.75em;
|
||||||
&::after {
|
position: relative;
|
||||||
font-size: 0.75em;
|
bottom: 0.375rem;
|
||||||
position: relative;
|
color: var(--accent-color);
|
||||||
bottom: 0.375rem;
|
|
||||||
color: var(--accent-color);
|
|
||||||
content: counter(sidenote);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(max-width: $sidenote-breakpoint) {
|
@media(max-width: $sidenote-breakpoint) {
|
||||||
&::after {
|
&:hover {
|
||||||
content: "[" counter(sidenote) "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover::after {
|
|
||||||
color: var(--content-color);
|
color: var(--content-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only top-level anchors get brackets
|
||||||
|
&:not(.nested)::before {
|
||||||
|
content: '[';
|
||||||
|
}
|
||||||
|
&:not(.nested)::after {
|
||||||
|
content: ']';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.counter.floating {
|
||||||
|
position: absolute;
|
||||||
|
transform: translateX(calc(-100% - 0.4em));
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
// hidden checkbox that tracks the state of the mobile sidenote
|
// hidden checkbox that tracks the state of the mobile sidenote
|
||||||
.sidenote-toggle {
|
.sidenote-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
@ -50,7 +55,7 @@
|
|||||||
@media(min-width: $sidenote-breakpoint) {
|
@media(min-width: $sidenote-breakpoint) {
|
||||||
// max sidenote width is 20rem, if the window is too small then it's
|
// max sidenote width is 20rem, if the window is too small then it's
|
||||||
// the width of the gutter, minus the gap between sidenote and gutter,
|
// the width of the gutter, minus the gap between sidenote and gutter,
|
||||||
// minus an extra 1.5rem to account for the scrollbar on the right
|
// minus an extra 1.5rem to account for the scrollbar on the right
|
||||||
--gap: 2.5rem;
|
--gap: 2.5rem;
|
||||||
--gutter-width: calc(50vw - var(--content-width) / 2);
|
--gutter-width: calc(50vw - var(--content-width) / 2);
|
||||||
--sidenote-width: min(
|
--sidenote-width: min(
|
||||||
@ -64,7 +69,7 @@
|
|||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: $sidenote-breakpoint) {
|
@media(max-width: $sidenote-breakpoint) {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -88,6 +93,9 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
// when moving hidden -> shown, ease-out
|
// when moving hidden -> shown, ease-out
|
||||||
transition-timing-function: ease-out;
|
transition-timing-function: ease-out;
|
||||||
|
// the active sidenote should be on top of any other sidenotes as well
|
||||||
|
// (this isn't critical unless you have JS disabled, but it's still annoying)
|
||||||
|
z-index: 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,11 +104,11 @@
|
|||||||
max-width: var(--content-width);
|
max-width: var(--content-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
&.nested {
|
||||||
transform: translateX(calc(-100% - 0.4em));
|
margin-right: 0;
|
||||||
content: counter(sidenote);
|
margin-top: 0.75rem;
|
||||||
color: var(--accent-color);
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,65 +145,75 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nesting still needs work
|
// nesting still needs work
|
||||||
@media(min-width: $sidenote-breakpoint) {
|
/* @media(min-width: $sidenote-breakpoint) {
|
||||||
.nested.sidenote {
|
.nested.sidenote {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-top: 0.7rem;
|
margin-top: 0.7rem;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script context="module">
|
<script context="module">
|
||||||
var activeToggle = null;
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
let activeSidenote = writable(null);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
export let count;
|
||||||
|
|
||||||
let noteBody;
|
let noteBody;
|
||||||
let nested = false;
|
let nested = false;
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// check to see if the parent node is also a sidenote, if so move this one to the end
|
// check to see if the parent node is also a sidenote, if so move this one to the end
|
||||||
let parentNote = noteBody.parentElement.closest('span.sidenote');
|
let parentContent = noteBody.parentElement.closest('div.sidenote-content');
|
||||||
if (parentNote) {
|
if (parentContent) {
|
||||||
|
// extract just the content of the nested note, ditch the rest (i.e. the button)
|
||||||
|
const noteContent = noteBody.firstChild;
|
||||||
noteBody.remove();
|
noteBody.remove();
|
||||||
parentNote.appendChild(noteBody);
|
parentContent.appendChild(noteContent);
|
||||||
nested = true;
|
nested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const id = Math.random().toString().slice(2);
|
|
||||||
let toggle;
|
let toggle;
|
||||||
|
activeSidenote.subscribe(activeCount => {
|
||||||
|
// if we were the active toggle, but are no longer, hide
|
||||||
|
if (toggle?.checked && activeCount !== count) {
|
||||||
|
toggle.checked = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function toggleState() {
|
function toggleState() {
|
||||||
if (activeToggle === toggle) {
|
// if we are the active sidenote, deactivate us (upating the store will trigger subscription)
|
||||||
activeToggle = null;
|
if ($activeSidenote === count) {
|
||||||
}
|
$activeSidenote = null;
|
||||||
else if (activeToggle !== null) {
|
|
||||||
activeToggle.checked = false;
|
|
||||||
activeToggle = toggle;
|
|
||||||
}
|
}
|
||||||
|
// otherwise, we are becoming active
|
||||||
else {
|
else {
|
||||||
activeToggle = toggle;
|
$activeSidenote = count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label for={id} on:click={toggleState} class="counter"></label>
|
<label for={count} class="counter anchor" class:nested>{count}</label>
|
||||||
<input {id} bind:this={toggle} type="checkbox" class="sidenote-toggle" />
|
<input id={count} bind:this={toggle} on:click={toggleState} type="checkbox" class="sidenote-toggle" />
|
||||||
<!-- outer element so that on mobile it can extend the whole width of the viewport -->
|
<!-- outer element so that on mobile it can extend the whole width of the viewport -->
|
||||||
<div class="sidenote" class:nested bind:this={noteBody}>
|
<div class="sidenote" bind:this={noteBody}>
|
||||||
<!-- inner element so that content can be centered -->
|
<!-- inner element so that content can be centered -->
|
||||||
<div class="sidenote-content">
|
<div class="sidenote-content" class:nested>
|
||||||
|
<span class="counter floating">{count}</span>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<button class="dismiss" on:click={toggleState}>
|
|
||||||
<label for={id}>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-6 h-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button class="dismiss">
|
||||||
|
<label for={count}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-6 h-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@ -12,6 +12,7 @@ export function localRehype() {
|
|||||||
const needsDropcap = vfile.data.fm.dropcap !== false
|
const needsDropcap = vfile.data.fm.dropcap !== false
|
||||||
let dropcapAdded = false;
|
let dropcapAdded = false;
|
||||||
|
|
||||||
|
let sidenotesCount = 0;
|
||||||
let moduleScript;
|
let moduleScript;
|
||||||
let imports = new Set();
|
let imports = new Set();
|
||||||
if (needsDropcap) {
|
if (needsDropcap) {
|
||||||
@ -35,7 +36,13 @@ export function localRehype() {
|
|||||||
if (needsDropcap && !dropcapAdded && isParagraph(node)) {
|
if (needsDropcap && !dropcapAdded && isParagraph(node)) {
|
||||||
addDropcap(node);
|
addDropcap(node);
|
||||||
dropcapAdded = true;
|
dropcapAdded = true;
|
||||||
return SKIP;
|
}
|
||||||
|
|
||||||
|
// add `count` prop to each <Sidenote> component
|
||||||
|
if (isSidenote(node)) {
|
||||||
|
// increment the counter first so that the count starts at 1
|
||||||
|
sidenotesCount += 1;
|
||||||
|
addSidenoteCount(node, sidenotesCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -52,6 +59,9 @@ export function localRehype() {
|
|||||||
|
|
||||||
moduleScript.value = `${openingTag}\n\t${importScript}${remainder}`;
|
moduleScript.value = `${openingTag}\n\t${importScript}${remainder}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const name = vfile.filename.split('/').findLast(() => true);
|
||||||
|
// writeFileSync(`scratch/${name}.json`, JSON.stringify(tree, undefined, 4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +88,17 @@ function addDropcap(par) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function addSidenoteCount(node, count) {
|
||||||
|
// get the index of the closing >
|
||||||
|
const i = node.value.search(/>\s*$/);
|
||||||
|
if (i < 0) {
|
||||||
|
throw new Error('Failed to add counter to element, closing angle bracket not found.');
|
||||||
|
}
|
||||||
|
// splice in the count prop
|
||||||
|
node.value = `${node.value.slice(0, i)} count={${count}}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function isHeading(node) {
|
function isHeading(node) {
|
||||||
return node.type === 'element' && node.tagName.match(/h[1-6]/);
|
return node.type === 'element' && node.tagName.match(/h[1-6]/);
|
||||||
}
|
}
|
||||||
@ -89,3 +110,7 @@ function isModuleScript(node) {
|
|||||||
function isParagraph(node) {
|
function isParagraph(node) {
|
||||||
return node.type === 'element' && node.tagName === 'p';
|
return node.type === 'element' && node.tagName === 'p';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSidenote(node) {
|
||||||
|
return node.type === 'raw' && node.value.match(/<\s*Sidenote/);
|
||||||
|
}
|
||||||
|
@ -7,13 +7,13 @@ date: 2024-07-06
|
|||||||
import Sidenote from '$lib/Sidenote.svelte';
|
import Sidenote from '$lib/Sidenote.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
Like a lot of people, my main experience with private keys has come from using them for SSH. I'm familiar with the theory, of course - I know generally what asymmetric encryption does,<Sidenote>Although exactly _how_ it does so is still a complete mystery to me. I've looked up descriptions of RSA several times, and even tried to work my way through a toy example, but it's never helped. And I couldn't even _begin_ to explain elliptic curve cryptography beyond "black math magic".</Sidenote> and I know that it means a compromised server can't reveal your private key, which is nice although if you only ever use a given private key to SSH into your server and the server is already compromised, is that really so helpful?<Sidenote>Yes, yes, I know that it means you can use the same private key for _multiple_ things without having to worry, but in practice a lot of people seem to use separate private keys for separate things, and even though I'm not entirely sure why I feel uncomfortable doing otherwise.</Sidenote>
|
Like a lot of people, my main experience with private keys has come from using them for SSH. I'm familiar with the theory, of course - I know generally what asymmetric encryption does,<Sidenote>Although exactly _how_ it does so is still a complete mystery to me. I've looked up descriptions of RSA several times,<Sidenote>Testing nested notes again.</Sidenote> and even tried to work my way through a toy example, but it's never helped. And I couldn't even _begin_ to explain elliptic curve cryptography beyond "black math magic".</Sidenote> and I know that it means a compromised server can't reveal your private key, which is nice although if you only ever use a given private key to SSH into your server and the server is already compromised, is that really so helpful?<Sidenote>Yes, yes, I know that it means you can use the same private key for _multiple_ things without having to worry, but in practice a lot of people seem to use separate private keys for separate things, and even though I'm not entirely sure why I feel uncomfortable doing otherwise.</Sidenote>
|
||||||
|
|
||||||
What I was less aware of, however, was the various ways in which private keys can be _stored_, which rather suddenly became a more-than-purely-academic concern to me this past week. I had an old private key lying around which had originally been generated by AWS, and used a rather old format,<Sidenote>The oldest, I believe, that's in widespread use still.</Sidenote> and I needed it to be comprehensible by newer software which loftily refused to have anything to do with such outdated ways of expressing itself.<Sidenote>Who would write such obdurately high-handed software, you ask? Well, uh. Me, as it turns out. In my defense, though, I doubt it would have taken _less_ time to switch to a different SSH-key library than to figure out the particular magic incantation needed to get `ssh-keygen` to do it.</Sidenote> No problem, thought I, I'll just use `ssh-keygen` to convert the old format to a newer format! Unfortunately this was frustratingly<Sidenote>And needlessly, it seems to me?</Sidenote> difficult to figure out, so I'm writing it up here for posterity and so that I never have to look it up again.<Sidenote>You know how it works. Once you've taken the time to really describe process in detail, you have it locked in and never have to refer back to your notes.</Sidenote>
|
What I was less aware of, however, was the various ways in which private keys can be _stored_, which rather suddenly became a more-than-purely-academic concern to me this past week. I had an old private key lying around which had originally been generated by AWS, and used a rather old format,<Sidenote>The oldest, I believe, that's in widespread use still.</Sidenote> and I needed it to be comprehensible by newer software which loftily refused to have anything to do with such outdated ways of expressing itself.<Sidenote>Who would write such obdurately high-handed software, you ask? Well, uh. Me, as it turns out. In my defense, though, I doubt it would have taken _less_ time to switch to a different SSH-key library than to figure out the particular magic incantation needed to get `ssh-keygen` to do it.</Sidenote> No problem, thought I, I'll just use `ssh-keygen` to convert the old format to a newer format! Unfortunately this was frustratingly<Sidenote>And needlessly, it seems to me?</Sidenote> difficult to figure out, so I'm writing it up here for posterity and so that I never have to look it up again.<Sidenote>You know how it works. Once you've taken the time to really describe process in detail, you have it locked in and never have to refer back to your notes.</Sidenote>
|
||||||
|
|
||||||
## Preamble: Fantastic Formats and Where to Find Them
|
## Preamble: Fantastic Formats and Where to Find Them
|
||||||
|
|
||||||
If you're like me, you're probably aware that private keys are usually delivered as big blobs of Base64-encoded text prefaced by headers like `-----BEGIN OPENSSH PRIVATE KEY----`, and for some reason never use file extensions.<Sidenote>Well, for the ones generated by `ssh-keygen`, at least. OpenSSL-generated ones often use `.key` or `.pem`, but those aren't typically used for SSH, so are less relevant here.</Sidenote> There are three common formats you're likely to encounter in the wild:
|
I was aware, of course, that private keys are usually delivered as files containing big blobs of Base64-encoded text, prefaced by headers like `-----BEGIN OPENSSH PRIVATE KEY----`, and for whatever reason lacking file extensions.<Sidenote>Well, for the ones generated by `ssh-keygen`, at least. OpenSSL-generated ones often use `.key` or `.pem`, but those aren't typically used for SSH, so are less relevant here.</Sidenote> What I wasn't aware of is that there are actually several _different_ such formats, which although they look quite similar from the outside are internally pretty different. There are three you're likely to encounter in the wild:
|
||||||
|
|
||||||
1. OpenSSH-formatted keys, which start with `BEGIN OPENSSH KEY`<Sidenote>Plus the leading and trailing five dashes, but I'm tired of typing those out.</Sidenote> and are the preferred way of formatting private keys for use with SSH,
|
1. OpenSSH-formatted keys, which start with `BEGIN OPENSSH KEY`<Sidenote>Plus the leading and trailing five dashes, but I'm tired of typing those out.</Sidenote> and are the preferred way of formatting private keys for use with SSH,
|
||||||
2. PCKS#8-formatted keys, which start with `BEGIN PRIVATE KEY` or `BEGIN ENCRYPTED PRIVATE KEY`, and
|
2. PCKS#8-formatted keys, which start with `BEGIN PRIVATE KEY` or `BEGIN ENCRYPTED PRIVATE KEY`, and
|
||||||
@ -37,7 +37,7 @@ Both PKCS#1 and PKCS#8 use the same method for encoding the actual key data (whi
|
|||||||
|
|
||||||
## The `ssh-keygen` Manpage is a Tapestry of Lies
|
## The `ssh-keygen` Manpage is a Tapestry of Lies
|
||||||
|
|
||||||
So, I thought, I can use `ssh-keygen` to convert between these various and sundry formats, right? It can do that, it _has_ to be able to do that, right?
|
So, I thought, I can use `ssh-keygen` to convert between these various and sundry formats, right? It can do that, it _has_ to be able to do that, right?
|
||||||
|
|
||||||
Well, yes. It _can_, but good luck figuring out _how_. For starters, like many older CLI tools, `ssh-keygen` has an awful lot of flags and options, and it's hard to distinguish between which are _modifiers_ - "do the same thing, but differently" - and _modes of operation_ - "do a different thing entirely". The modern way to handle this distinction is with subcommands which take entirely different sets of arguments, but `ssh-keygen` dates back to a time before that was common.
|
Well, yes. It _can_, but good luck figuring out _how_. For starters, like many older CLI tools, `ssh-keygen` has an awful lot of flags and options, and it's hard to distinguish between which are _modifiers_ - "do the same thing, but differently" - and _modes of operation_ - "do a different thing entirely". The modern way to handle this distinction is with subcommands which take entirely different sets of arguments, but `ssh-keygen` dates back to a time before that was common.
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ Most probably this is just a case of a tool evolving organically over time rathe
|
|||||||
|
|
||||||
## Imagining a Brighter Tomorrow
|
## Imagining a Brighter Tomorrow
|
||||||
|
|
||||||
But it doesn't have to be this way! Nothing (that I can see) prevents the `-i` option from being updated to accept private keys as well as public keys: it's clearly perfectly capable of telling when a file _isn't_ a valid _public_ key in the specified format, so it it seems like it could just parse it as a private key instead, and keep going if successful. Or an entirely new option could be added for converting private keys. `-c` is already taken for changing comments, but there are a few letters remaining. I don't see a `-j` on the manpage, for instance, or an `-x`.
|
But it doesn't have to be this way! Nothing (that I can see) prevents the `-i` option from being updated to accept private keys as well as public keys: it's clearly perfectly capable of telling when a file _isn't_ a valid _public_ key in the specified format, so it it seems like it could just parse it as a private key instead, and keep going if successful. Or an entirely new option could be added for converting private keys. `-c` is already taken for changing comments, but there are a few letters remaining. I don't see a `-j` on the manpage, for instance, or an `-x`.
|
||||||
|
|
||||||
I realize that an unforgiving reading of my travails in this endeavour might yield the conclusion that I'm an idiot with no reading comprehension, and that the manpage _clearly_ stated the solution to my problem _all along_, and if I had just RTFM<Sidenote>Noob.</Sidenote> then I could have avoided all this frustration, but that seems [a little unfair](https://xkcd.com/293/) to me.<Sidenote>Besides, I'm annoyed, and it's more satisfying to blame others than admit any fault of my own.</Sidenote> When you're writing the help message or manpage for your tool, you should _expect_ that people will be skimming it, looking to pick out the tidbits that are important to them right now, since for any tool of reasonable complexity 95% of the documentation is going to be irrelevant to any single user in any single situation.
|
I realize that an unforgiving reading of my travails in this endeavour might yield the conclusion that I'm an idiot with no reading comprehension, and that the manpage _clearly_ stated the solution to my problem _all along_, and if I had just RTFM<Sidenote>Noob.</Sidenote> then I could have avoided all this frustration, but that seems [a little unfair](https://xkcd.com/293/) to me.<Sidenote>Besides, I'm annoyed, and it's more satisfying to blame others than admit any fault of my own.</Sidenote> When you're writing the help message or manpage for your tool, you should _expect_ that people will be skimming it, looking to pick out the tidbits that are important to them right now, since for any tool of reasonable complexity 95% of the documentation is going to be irrelevant to any single user in any single situation.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user