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
@@ -181,6 +181,12 @@ longer than 200 characters. The board has
|
|||||||
`:board`, `:card <title>` (`:card!` links the open document to it),
|
`:board`, `:card <title>` (`:card!` links the open document to it),
|
||||||
`:due <when|->`, `:repeat <rule|->`, `:done` and `:list <name>` (`:list!` deletes one).
|
`:due <when|->`, `:repeat <rule|->`, `:done` and `:list <name>` (`:list!` deletes one).
|
||||||
|
|
||||||
|
A single line break in a document is a line break, the way it was typed. A
|
||||||
|
paragraph an editor wrapped at a fixed width — an import, a save over the mount
|
||||||
|
from vim with `textwidth` — is the exception: every line but the last is long
|
||||||
|
and each break comes where the next word would not have fitted, so it is read
|
||||||
|
as running text. Two spaces or a `\` at the end of a line always break it.
|
||||||
|
|
||||||
On a touch device modal editing is off by default (`:set vim` turns it on) and
|
On a touch device modal editing is off by default (`:set vim` turns it on) and
|
||||||
the same actions are buttons: the `⋯` on a row — or a long press — opens what
|
the same actions are buttons: the `⋯` on a row — or a long press — opens what
|
||||||
the keys do to it: rename, move, share, seal, delete. A move is picked up
|
the keys do to it: rename, move, share, seal, delete. A move is picked up
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ Each save keeps the text it replaces, so nothing written is lost. See
|
|||||||
## Markdown
|
## Markdown
|
||||||
|
|
||||||
Documents are GitHub-flavoured markdown: headings, lists, task lists, tables,
|
Documents are GitHub-flavoured markdown: headings, lists, task lists, tables,
|
||||||
quotes, code blocks and links. A single line break is kept as a line break.
|
quotes, code blocks and links. A single line break is kept as a line break,
|
||||||
|
the way it was typed, with one exception: a paragraph that an editor wrapped
|
||||||
|
at a fixed width, as imported notes and files saved over the SFTP mount often
|
||||||
|
are, is read as the running text it is. A break asked for outright, with two
|
||||||
|
spaces or a `\` at the end of the line, is always kept.
|
||||||
|
|
||||||
Math is set by KaTeX:
|
Math is set by KaTeX:
|
||||||
|
|
||||||
|
|||||||
+18
-1
@@ -1,7 +1,7 @@
|
|||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
import { marked, type TokenizerAndRendererExtension } from 'marked'
|
import { marked, type TokenizerAndRendererExtension } from 'marked'
|
||||||
import { mathEngine } from './math.svelte'
|
import { mathEngine } from './math.svelte'
|
||||||
import { titleHref } from './util'
|
import { hardWrapped, titleHref } from './util'
|
||||||
|
|
||||||
marked.setOptions({ gfm: true, breaks: true })
|
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({
|
marked.use({
|
||||||
extensions: [mathBlock, mathInline, wikilink],
|
extensions: [mathBlock, mathInline, wikilink],
|
||||||
|
walkTokens(token) {
|
||||||
|
if (token.type === 'paragraph' && hardWrapped(token.raw)) unwrap(token.tokens as Inline[])
|
||||||
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
code(token) {
|
code(token) {
|
||||||
return token.lang === 'math' ? tex(token.text, true) : false
|
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
|
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