mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-03 16:41:27 +01:00
markdown: a paragraph an editor wrapped reads as running text
`breaks` is on because the diary is written a line at a time and a line typed in the browser is meant. Markdown written elsewhere is usually wrapped at a width instead — an import, a save over the SFTP mount from vim with `textwidth`, a pasted README — and kept its breaks, one in the middle of every sentence, worst on a phone. `hardWrapped` tells the two apart by what a wrapping editor leaves behind: every line but the last is long, each break comes where the next word would not have fitted on the line before, and the lines seldom end where a sentence does. A paragraph that looks like that has its soft breaks read as spaces; a break asked for outright, two spaces or a backslash at the end of the line, stays. Lines broken by hand — a short sentence and then another, a list of things that happened, a verse — keep their breaks as before.
This commit is contained in:
1 parent
a59ba4af1b
commit
767dd076dd
4 files changed
+53
-2
No files matched your search
+18
-1
@@ -1,7 +1,7 @@
|
||||
import DOMPurify from 'dompurify'
|
||||
import { marked, type TokenizerAndRendererExtension } from 'marked'
|
||||
import { mathEngine } from './math.svelte'
|
||||
import { titleHref } from './util'
|
||||
import { hardWrapped, titleHref } from './util'
|
||||
|
||||
marked.setOptions({ gfm: true, breaks: true })
|
||||
|
||||
@@ -68,8 +68,25 @@ const wikilink: TokenizerAndRendererExtension = {
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
* A line break typed in the browser is meant: the diary is written a line at a
|
||||
* time, so `breaks` is on. A paragraph an editor wrapped at its width is the
|
||||
* exception, and its breaks are read as the spaces they stand for. A break
|
||||
* asked for outright — two spaces or a `\` at the end of the line — stays.
|
||||
*/
|
||||
type Inline = { type: string; raw: string; tokens?: Inline[] }
|
||||
function unwrap(tokens: Inline[]) {
|
||||
for (const [at, token] of tokens.entries()) {
|
||||
if (token.type === 'br' && /^ ?\n$/.test(token.raw)) tokens[at] = { type: 'text', raw: token.raw, text: ' ' } as Inline
|
||||
else if (token.tokens) unwrap(token.tokens)
|
||||
}
|
||||
}
|
||||
|
||||
marked.use({
|
||||
extensions: [mathBlock, mathInline, wikilink],
|
||||
walkTokens(token) {
|
||||
if (token.type === 'paragraph' && hardWrapped(token.raw)) unwrap(token.tokens as Inline[])
|
||||
},
|
||||
renderer: {
|
||||
code(token) {
|
||||
return token.lang === 'math' ? tex(token.text, true) : false
|
||||
|
||||
@@ -322,3 +322,27 @@ export function neighbour(rects: Rect[], from: number, direction: Direction): nu
|
||||
})
|
||||
return best
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a paragraph was wrapped by an editor at a fixed width rather than
|
||||
* broken by hand: every line but the last is long, and each break comes
|
||||
* where the next word would not have fitted on the line before. Markdown
|
||||
* written elsewhere — an import, a save over the SFTP mount from vim with
|
||||
* `textwidth`, a pasted README — is wrapped that way, and keeping its breaks
|
||||
* puts one in the middle of every sentence. Lines broken by hand rarely pass:
|
||||
* a short sentence followed by a short word would have fitted, and a line
|
||||
* broken by hand mostly ends where a sentence does, which a wrapped one
|
||||
* seldom does.
|
||||
*/
|
||||
export function hardWrapped(paragraph: string): boolean {
|
||||
const lines = paragraph.trimEnd().split('\n').map((line) => line.trimEnd())
|
||||
if (lines.length < 2) return false
|
||||
const body = lines.slice(0, -1)
|
||||
const width = Math.max(...lines.map((line) => line.length))
|
||||
const fits = body.every((line, at) => {
|
||||
const next = lines[at + 1].trimStart().split(/\s/, 1)[0]
|
||||
return line.length >= 40 && line.length + 1 + next.length > width
|
||||
})
|
||||
const sentences = body.filter((line) => /[.!?:;]$/.test(line)).length
|
||||
return fits && sentences <= Math.floor((body.length - 1) / 3)
|
||||
}
|
||||
Reference in new issue
Block a user