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
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
**/_test.*
|
||||
**/_test.*
|
||||
/scratch
|
@ -8,31 +8,36 @@
|
||||
counter-reset: sidenote;
|
||||
}
|
||||
|
||||
.counter {
|
||||
counter-increment: sidenote;
|
||||
.counter.anchor {
|
||||
color: #444;
|
||||
margin-left: 0.065rem;
|
||||
|
||||
&::after {
|
||||
font-size: 0.75em;
|
||||
position: relative;
|
||||
bottom: 0.375rem;
|
||||
color: var(--accent-color);
|
||||
content: counter(sidenote);
|
||||
}
|
||||
font-size: 0.75em;
|
||||
position: relative;
|
||||
bottom: 0.375rem;
|
||||
color: var(--accent-color);
|
||||
|
||||
@media(max-width: $sidenote-breakpoint) {
|
||||
&::after {
|
||||
content: "[" counter(sidenote) "]";
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
&:hover {
|
||||
color: var(--content-color);
|
||||
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
|
||||
.sidenote-toggle {
|
||||
display: none;
|
||||
@ -50,7 +55,7 @@
|
||||
@media(min-width: $sidenote-breakpoint) {
|
||||
// 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,
|
||||
// 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;
|
||||
--gutter-width: calc(50vw - var(--content-width) / 2);
|
||||
--sidenote-width: min(
|
||||
@ -64,7 +69,7 @@
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
@media(max-width: $sidenote-breakpoint) {
|
||||
@media(max-width: $sidenote-breakpoint) {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@ -88,6 +93,9 @@
|
||||
transform: translateY(0);
|
||||
// when moving hidden -> shown, 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);
|
||||
margin: 0 auto;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
transform: translateX(calc(-100% - 0.4em));
|
||||
content: counter(sidenote);
|
||||
color: var(--accent-color);
|
||||
|
||||
&.nested {
|
||||
margin-right: 0;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,65 +145,75 @@
|
||||
}
|
||||
|
||||
// nesting still needs work
|
||||
@media(min-width: $sidenote-breakpoint) {
|
||||
/* @media(min-width: $sidenote-breakpoint) {
|
||||
.nested.sidenote {
|
||||
margin-right: 0;
|
||||
margin-top: 0.7rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
} */
|
||||
</style>
|
||||
|
||||
<script context="module">
|
||||
var activeToggle = null;
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
let activeSidenote = writable(null);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let count;
|
||||
|
||||
let noteBody;
|
||||
let nested = false;
|
||||
onMount(() => {
|
||||
// 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');
|
||||
if (parentNote) {
|
||||
let parentContent = noteBody.parentElement.closest('div.sidenote-content');
|
||||
if (parentContent) {
|
||||
// extract just the content of the nested note, ditch the rest (i.e. the button)
|
||||
const noteContent = noteBody.firstChild;
|
||||
noteBody.remove();
|
||||
parentNote.appendChild(noteBody);
|
||||
parentContent.appendChild(noteContent);
|
||||
nested = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
const id = Math.random().toString().slice(2);
|
||||
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() {
|
||||
if (activeToggle === toggle) {
|
||||
activeToggle = null;
|
||||
}
|
||||
else if (activeToggle !== null) {
|
||||
activeToggle.checked = false;
|
||||
activeToggle = toggle;
|
||||
// if we are the active sidenote, deactivate us (upating the store will trigger subscription)
|
||||
if ($activeSidenote === count) {
|
||||
$activeSidenote = null;
|
||||
}
|
||||
// otherwise, we are becoming active
|
||||
else {
|
||||
activeToggle = toggle;
|
||||
$activeSidenote = count;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<label for={id} on:click={toggleState} class="counter"></label>
|
||||
<input {id} bind:this={toggle} type="checkbox" class="sidenote-toggle" />
|
||||
<label for={count} class="counter anchor" class:nested>{count}</label>
|
||||
<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 -->
|
||||
<div class="sidenote" class:nested bind:this={noteBody}>
|
||||
<div class="sidenote" bind:this={noteBody}>
|
||||
<!-- 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>
|
||||
<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>
|
||||
<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
|
||||
let dropcapAdded = false;
|
||||
|
||||
let sidenotesCount = 0;
|
||||
let moduleScript;
|
||||
let imports = new Set();
|
||||
if (needsDropcap) {
|
||||
@ -35,7 +36,13 @@ export function localRehype() {
|
||||
if (needsDropcap && !dropcapAdded && isParagraph(node)) {
|
||||
addDropcap(node);
|
||||
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}`;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return node.type === 'element' && node.tagName.match(/h[1-6]/);
|
||||
}
|
||||
@ -89,3 +110,7 @@ function isModuleScript(node) {
|
||||
function isParagraph(node) {
|
||||
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';
|
||||
</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>
|
||||
|
||||
## 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,
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@ -75,7 +75,7 @@ Most probably this is just a case of a tool evolving organically over time rathe
|
||||
|
||||
## 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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user