Compare commits

...

2 Commits

Author SHA1 Message Date
lilleman 6c499127b5 cjk - a letter of a script written without spaces bounds a highlight
CI / gate (push) Successful in 42s
CI / publish (push) Has been skipped
2026-09-29 18:43:29 +02:00
lilleman 151bf38398 cjk - tests for a highlight flush against a script written without spaces 2026-09-29 18:43:04 +02:00
5 changed files with 21 additions and 8 deletions
+1 -1
View File
@@ -133,7 +133,7 @@ read replaces mentions, attachments and macros with text.
| `panel` | a GitHub alert, `> [!WARNING]`: info `NOTE`, note `IMPORTANT`, tip and success `TIP`, warning `WARNING`, error `CAUTION`, custom `NOTE` | `NOTE` info, `IMPORTANT` note, `TIP` tip, `WARNING` warning, `CAUTION` error, and Obsidian's: hint tip; success, check, done success; attention warning; danger, failure, fail, missing, bug, error error; any other word info — in any case; the rest of the marker's line is the first paragraph | | `panel` | a GitHub alert, `> [!WARNING]`: info `NOTE`, note `IMPORTANT`, tip and success `TIP`, warning `WARNING`, error `CAUTION`, custom `NOTE` | `NOTE` info, `IMPORTANT` note, `TIP` tip, `WARNING` warning, `CAUTION` error, and Obsidian's: hint tip; success, check, done success; attention warning; danger, failure, fail, missing, bug, error error; any other word info — in any case; the rest of the marker's line is the first paragraph |
| `expand`, `nestedExpand` | Obsidian's folded callout, `> [!NOTE]- Title` | `-` or `+` after any word, the rest of the marker's line the title; an expand inside an expand is a `nestedExpand` | | `expand`, `nestedExpand` | Obsidian's folded callout, `> [!NOTE]- Title` | `-` or `+` after any word, the rest of the marker's line the title; an expand inside an expand is a `nestedExpand` |
| `taskList` | `- [x] Done`, `- [ ] Todo` | a bullet list whose every item is so marked, `[X]` too | | `taskList` | `- [x] Done`, `- [ ] Todo` | a bullet list whose every item is so marked, `[X]` too |
| `backgroundColor` | `==text==` | `==text==` on one line, the text touching both delimiters, bounded outside by whitespace, punctuation or a line edge, in the editor's default highlight `#f8e6a0` | | `backgroundColor` | `==text==` | `==text==` on one line, the text touching both delimiters, bounded outside by whitespace, punctuation, a line edge or a letter of a script written without spaces (Han, Hiragana, Katakana, Thai, Lao, Khmer, Myanmar), in the editor's default highlight `#f8e6a0` |
| `table` | a pipe table: the first row its header, a cell's blocks on one line, a span kept under its header by empty cells | — | | `table` | a pipe table: the first row its header, a cell's blocks on one line, a span kept under its header by empty cells | — |
| `decisionList` | a bullet list | — | | `decisionList` | a bullet list | — |
| `mention`, `status`, `emoji`, `date` | their text: `@` kept, a mention with none `@` and its id, an emoji its `shortName` without, a date `2026-09-13` in UTC | — | | `mention`, `status`, `emoji`, `date` | their text: `@` kept, a mention with none `@` and its id, an emoji its `shortName` without, a date `2026-09-13` in UTC | — |
+7 -5
View File
@@ -145,14 +145,16 @@ parentheses: `> [!faq]- See [x](http://y)` reads to the title `See x (http://y)`
## The plain flavour's spellings ## The plain flavour's spellings
2026-09-14, panels 2026-09-25, the maintainer. Goal 5. Valid while GitHub's renderer is the one the 2026-09-14, panels 2026-09-25 and 2026-09-29, the maintainer. Goal 5. Valid while GitHub's
audience's markdown is read in. renderer is the one the audience's markdown is read in.
README §Plain markdown's rows come from a survey of GitHub, GitLab, Gitea, Obsidian, Pandoc, README §Plain markdown's rows come from a survey of GitHub, GitLab, Gitea, Obsidian, Pandoc,
MkDocs, Docusaurus, Typora, Joplin, Logseq, Bear, Notion, Azure DevOps and Discord, GitHub's MkDocs, Docusaurus, Typora, Joplin, Logseq, Bear, Notion, Azure DevOps and Discord, GitHub's
renderer confirming each shape. Reader panels settled `error` as an error panel and the `==` renderer confirming each shape. Reader panels settled `error` as an error panel, the `==` bounds
bounds (3 of 3), the external image's two forms (6 of 7), the omission notes and a rule opening a (3 of 3) and a letter of a script written without spaces bounding them, so `は==日本語==で`
list item dropping (3 of 3), and a list's numbering overflowing into bullets (3 of 3, 5 of 7). highlights (3 of 3), the external image's two forms (6 of 7), the omission notes and a rule
opening a list item dropping (3 of 3), and a list's numbering overflowing into bullets (3 of 3,
5 of 7).
Reading takes other tools' spellings, since it reads their output and writes none of them. Reading takes other tools' spellings, since it reads their output and writes none of them.
Rejected: `~sub~` and `^sup^` (`~2~` is a strike on GitHub, so `subsup` drops), underline and colour Rejected: `~sub~` and `^sup^` (`~2~` is a strike on GitHub, so `subsup` drops), underline and colour
spellings, raw HTML (`<details>`, `<mark>`), MkDocs `!!!` and the `:::` admonition family, spellings, raw HTML (`<details>`, `<mark>`), MkDocs `!!!` and the `:::` admonition family,
@@ -148,6 +148,8 @@ test('spells a highlight as a == pair around the run, whatever its colour', () =
assert.equal(plain(paragraph(text('a', highlight('#fff'), code), text('b', highlight('#fff')))), '`a`==b==\n') assert.equal(plain(paragraph(text('a', highlight('#fff'), code), text('b', highlight('#fff')))), '`a`==b==\n')
assert.equal(plain(paragraph(text('=', highlight('#fff')), text(' '), text('a==b', highlight('#fff')))), '==\\=== ==a==b==\n') assert.equal(plain(paragraph(text('=', highlight('#fff')), text(' '), text('a==b', highlight('#fff')))), '==\\=== ==a==b==\n')
assert.equal(plain(paragraph(text('x'), text('y', highlight('#fff')), text(' z'))), 'xy z\n') assert.equal(plain(paragraph(text('x'), text('y', highlight('#fff')), text(' z'))), 'xy z\n')
assert.equal(plain(paragraph(text('この機能は'), text('日本語', highlight('#fff')), text('でのみ'))), 'この機能は==日本語==でのみ\n')
assert.equal(plain(paragraph(text('日==本==語'))), '日\\==本\\==語\n')
}) })
test('escapes text a renderer would take as a flavour marker, and only there', () => { test('escapes text a renderer would take as a flavour marker, and only there', () => {
@@ -149,6 +149,9 @@ test('reads a == pair to the editor default highlight, Yellow200 #f8e6a0 in @atl
assert.deepEqual(read('==`a`==\n'), [paragraph(text('a', code))]) assert.deepEqual(read('==`a`==\n'), [paragraph(text('a', code))])
assert.deepEqual(read('x==y==z ==a == b==, (==c==) _d_==e==\n'), [paragraph(text('x==y==z '), text('a == b', highlight), text(', ('), text('c', highlight), text(') '), text('d', em), text('e', highlight))]) assert.deepEqual(read('x==y==z ==a == b==, (==c==) _d_==e==\n'), [paragraph(text('x==y==z '), text('a == b', highlight), text(', ('), text('c', highlight), text(') '), text('d', em), text('e', highlight))])
assert.deepEqual(read('😀==b== ==c==😀 é==d==\n'), [paragraph(text('😀'), text('b', highlight), text(' '), text('c', highlight), text('😀 é==d=='))]) assert.deepEqual(read('😀==b== ==c==😀 é==d==\n'), [paragraph(text('😀'), text('b', highlight), text(' '), text('c', highlight), text('😀 é==d=='))])
assert.deepEqual(read('この機能は==日本語==でのみ、中文==重点==内容、ภาษา==ไทย==ดี a==日本==b\n'), [
paragraph(text('この機能は'), text('日本語', highlight), text('でのみ、中文'), text('重点', highlight), text('内容、ภาษา'), text('ไทย', highlight), text('ดี a==日本==b')),
])
assert.deepEqual(read('# ==h==\n\n| ==c== |\n| --- |\n'), [ assert.deepEqual(read('# ==h==\n\n| ==c== |\n| --- |\n'), [
node('heading', { level: 1 }, text('h', highlight)), node('heading', { level: 1 }, text('h', highlight)),
bare('table', bare('tableRow', bare('tableHeader', paragraph(text('c', highlight))))), bare('table', bare('tableRow', bare('tableHeader', paragraph(text('c', highlight))))),
@@ -196,7 +199,7 @@ test('reads what the reduction wrote back to the node it reduced, less the attri
test('reads text the writer kept from reading as a marker back as text', () => { test('reads text the writer kept from reading as a marker back as text', () => {
const quote = bare('blockquote', said('[!NOTE] x')) const quote = bare('blockquote', said('[!NOTE] x'))
const list = bare('bulletList', bare('listItem', said('[x] a')), bare('listItem', said('[ ] b'))) const list = bare('bulletList', bare('listItem', said('[x] a')), bare('listItem', said('[ ] b')))
assert.deepEqual(roundTripped(said('==x== a==b'), quote, list), [said('==x== a==b'), quote, list]) assert.deepEqual(roundTripped(said('==x== a==b 日==本==語'), quote, list), [said('==x== a==b 日==本==語'), quote, list])
assert.deepEqual(roundTripped(bare('taskList', task('DONE', text('[x] ==a==')))), [bare('taskList', task('DONE', text('[x] ==a==')))]) assert.deepEqual(roundTripped(bare('taskList', task('DONE', text('[x] ==a==')))), [bare('taskList', task('DONE', text('[x] ==a==')))])
}) })
+7 -1
View File
@@ -34,6 +34,8 @@ const panelTypesByWord: Readonly<Record<string, string>> = {
warning: 'warning', warning: 'warning',
} }
const unspacedScript = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Khmer}\p{Script=Lao}\p{Script=Myanmar}\p{Script=Thai}]$/u
export function alertMarker(panelType: unknown): string { export function alertMarker(panelType: unknown): string {
const word = typeof panelType === 'string' && Object.hasOwn(alertWords, panelType) ? alertWords[panelType] : undefined const word = typeof panelType === 'string' && Object.hasOwn(alertWords, panelType) ? alertWords[panelType] : undefined
return `[!${word ?? 'NOTE'}]` return `[!${word ?? 'NOTE'}]`
@@ -60,7 +62,11 @@ export function highlightFlanking(source: string, index: number): { closes: bool
const end = index + highlightDelimiter.length const end = index + highlightDelimiter.length
const before = Array.from(source.slice(Math.max(0, index - 2), index)).at(-1) ?? '' const before = Array.from(source.slice(Math.max(0, index - 2), index)).at(-1) ?? ''
const after = Array.from(source.slice(end, end + 2))[0] ?? '' const after = Array.from(source.slice(end, end + 2))[0] ?? ''
return { closes: flanks(before) && !isWordCharacter(after), opens: flanks(after) && !isWordCharacter(before) } return { closes: flanks(before) && bounds(after), opens: flanks(after) && bounds(before) }
}
function bounds(character: string): boolean {
return !isWordCharacter(character) || unspacedScript.test(character)
} }
function flanks(character: string): boolean { function flanks(character: string): boolean {