@markup-carve/carve-grammars 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/highlightjs/carve.js +537 -0
- package/highlightjs/carve.mjs +25 -0
- package/package.json +119 -0
- package/prism/carve.js +471 -0
- package/shiki/carve.css +46 -0
- package/shiki/index.js +188 -0
- package/textmate/carve.tmLanguage.json +1023 -0
- package/tiptap/carve-kit.js +507 -0
- package/tiptap/extensions/carve-abbreviation.js +79 -0
- package/tiptap/extensions/carve-definition-list.js +270 -0
- package/tiptap/extensions/carve-delete.js +54 -0
- package/tiptap/extensions/carve-div.js +188 -0
- package/tiptap/extensions/carve-embed.js +248 -0
- package/tiptap/extensions/carve-footnote-definition.js +81 -0
- package/tiptap/extensions/carve-footnote.js +76 -0
- package/tiptap/extensions/carve-insert.js +54 -0
- package/tiptap/extensions/carve-keymap.js +71 -0
- package/tiptap/extensions/carve-math.js +82 -0
- package/tiptap/extensions/carve-mention.js +44 -0
- package/tiptap/extensions/carve-span.js +101 -0
- package/tiptap/extensions/carve-tabs.js +158 -0
- package/tiptap/extensions/index.js +14 -0
- package/tiptap/index.js +53 -0
- package/tiptap/serializer.js +686 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 php-collective
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Carve Grammars
|
|
2
|
+
|
|
3
|
+
Grammars for the [Carve](https://github.com/markup-carve/carve) markup language:
|
|
4
|
+
|
|
5
|
+
- a **Tiptap** integration (editor kit + serializer) that turns a Tiptap/ProseMirror document into Carve markup;
|
|
6
|
+
- **Prism** and **highlight.js** syntax-highlighting grammars for rendering Carve source on the web;
|
|
7
|
+
- a **TextMate** grammar (`textmate/carve.tmLanguage.json`) for TextMate-based highlighters such as Shiki (used by VitePress).
|
|
8
|
+
|
|
9
|
+
Modeled on [djot-grammars](https://github.com/php-collective/djot-grammars), adapted to Carve's syntax. The Tiptap mark mapping mirrors `carve-php`'s `HtmlToCarve` converter; the highlighting grammars mirror the canonical token set in [`carve/resources/grammar.ebnf`](https://github.com/markup-carve/carve) and the TextMate grammar in [vscode-carve](https://github.com/markup-carve/vscode-carve).
|
|
10
|
+
|
|
11
|
+
> **Status:** Tiptap integration, plus Prism, highlight.js and TextMate grammars.
|
|
12
|
+
> Sibling editor grammars live in their own repos: editor-bundled **TextMate** copies in
|
|
13
|
+
> [vscode-carve](https://github.com/markup-carve/vscode-carve) and
|
|
14
|
+
> [intellij-carve](https://github.com/markup-carve/intellij-carve);
|
|
15
|
+
> **Tree-sitter** in [tree-sitter-carve](https://github.com/markup-carve/tree-sitter-carve)
|
|
16
|
+
> and [zed-carve](https://github.com/markup-carve/zed-carve).
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @markup-carve/carve-grammars
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
All peer dependencies are optional - install only what you use:
|
|
25
|
+
`@tiptap/core` + `@tiptap/starter-kit` (v2) for the editor, `prismjs` (v1) for
|
|
26
|
+
Prism, `highlight.js` (v11) for highlight.js.
|
|
27
|
+
|
|
28
|
+
`CarveKit` also pulls in several standalone Tiptap marks/extensions (highlight,
|
|
29
|
+
subscript, superscript, underline, link, image, table, task-list); install the
|
|
30
|
+
`@tiptap/extension-*` packages you use, or disable them via
|
|
31
|
+
`CarveKit.configure({ underline: false, ... })`.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { Editor } from '@tiptap/core'
|
|
37
|
+
import { CarveKit, serializeToCarve } from '@markup-carve/carve-grammars/tiptap'
|
|
38
|
+
|
|
39
|
+
const editor = new Editor({
|
|
40
|
+
element: document.getElementById('editor'),
|
|
41
|
+
extensions: [CarveKit],
|
|
42
|
+
onUpdate: ({ editor }) => {
|
|
43
|
+
const carve = serializeToCarve(editor.getJSON())
|
|
44
|
+
console.log(carve)
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Individual extensions
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
53
|
+
import { CarveInsert, CarveDelete, CarveDiv, serializeToCarve } from '@markup-carve/carve-grammars/tiptap'
|
|
54
|
+
|
|
55
|
+
const editor = new Editor({
|
|
56
|
+
extensions: [StarterKit, CarveInsert, CarveDelete, CarveDiv],
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Mark mapping
|
|
61
|
+
|
|
62
|
+
| Tiptap mark | Carve token | Renders as |
|
|
63
|
+
|-------------|-------------|------------|
|
|
64
|
+
| bold | `*text*` / `{*text*}` | `<strong>` |
|
|
65
|
+
| italic | `/text/` / `{/text/}` | `<em>` |
|
|
66
|
+
| underline | `_text_` / `{_text_}` | `<u>` |
|
|
67
|
+
| code | `` `text` `` | `<code>` |
|
|
68
|
+
| highlight | `=text=` / `{=text=}` | `<mark>` |
|
|
69
|
+
| strike | `~text~` / `{~text~}` | `<s>` |
|
|
70
|
+
| subscript | `{,text,}` (braced only) | `<sub>` |
|
|
71
|
+
| superscript | `{^text^}` (braced only) | `<sup>` |
|
|
72
|
+
| insert | `{+text+}` | `<ins>` |
|
|
73
|
+
| delete | `{-text-}` | `<del>` |
|
|
74
|
+
| link | `[text](url)` / `[text](url "title")` | `<a>` |
|
|
75
|
+
| image | `` / `` | `<img>` |
|
|
76
|
+
| span | `[text]{.class}` | `<span class>` |
|
|
77
|
+
| abbreviation | `[text]{abbr="..."}` | `<abbr title>` \*\*\* |
|
|
78
|
+
|
|
79
|
+
\*\*\* `[text]{abbr="..."}` renders a real `<abbr title>` only when carve's
|
|
80
|
+
`SemanticSpanExtension` is enabled (the same opt-in extension also maps `{kbd}`
|
|
81
|
+
-> `<kbd>`, `{dfn}` -> `<dfn>`, `{samp}` -> `<samp>`, `{var}` -> `<var>`).
|
|
82
|
+
Without it, the attribute stays literal: `<span abbr="...">`. The mark's
|
|
83
|
+
`parseHTML` reads back the `<abbr title>` form.
|
|
84
|
+
|
|
85
|
+
The tokens target carve-php's **parser** (the contract: serialized Carve must parse
|
|
86
|
+
back to the same elements). Carve's inline syntax differs notably from Djot's:
|
|
87
|
+
emphasis is `/text/` (Djot uses `_`), `_text_` is underline, `~text~` is
|
|
88
|
+
strikethrough, highlight is `=text=`, and subscript/superscript are the
|
|
89
|
+
braced `{,text,}` / `{^text^}` only (a bare `,` or `^` is literal text since
|
|
90
|
+
carve #259).
|
|
91
|
+
|
|
92
|
+
Each single-char delimiter has two equivalent forms: a **bare** form
|
|
93
|
+
(`=text=`) and a **forced brace** form (`{=text=}`) that also works intraword;
|
|
94
|
+
both parse to the same element. The two columns above list bare / forced.
|
|
95
|
+
`serializeToCarve` emits the bare form for `* / _ ~` and the forced `{…}` form
|
|
96
|
+
for `= , ^` (round-trip-safe — those delimiters are likelier to be inert bare);
|
|
97
|
+
`{+…+}` / `{-…-}` (insert / delete) have only the brace form, since `+` / `-`
|
|
98
|
+
are not emphasis delimiters.
|
|
99
|
+
|
|
100
|
+
### Escaping
|
|
101
|
+
|
|
102
|
+
To honor that round-trip contract, `serializeToCarve` escapes literal Carve
|
|
103
|
+
syntax in plain text so it parses back as text rather than markup - inline code,
|
|
104
|
+
links, footnotes, CriticMarkup, mentions/tags/emoji, and an emphasis delimiter
|
|
105
|
+
appearing inside its own span. Escaping is **contextual**: Carve's flanking rules
|
|
106
|
+
already make most lone delimiters inert (`price * 2`, intraword `x_1`,
|
|
107
|
+
`comma,, two`, `C:\path`, `a@b.com`), so those stay clean. The same logic is
|
|
108
|
+
exposed as `escapeCarve(text)`.
|
|
109
|
+
|
|
110
|
+
## Block elements
|
|
111
|
+
|
|
112
|
+
Headings (`#`), bullet / ordered / task lists, blockquotes (`>`), fenced code
|
|
113
|
+
blocks (`` ``` lang ``), horizontal rules (`---`), tables (with `|=` header
|
|
114
|
+
cells and `^` / `<` row / column spans), container divs (`::: class`), and
|
|
115
|
+
definition lists.
|
|
116
|
+
|
|
117
|
+
## Syntax highlighting
|
|
118
|
+
|
|
119
|
+
Render Carve source as highlighted HTML on the web. Both grammars cover the full
|
|
120
|
+
Carve token set: headings, lists, tables, blockquotes, fenced/raw blocks,
|
|
121
|
+
container divs, front matter and comments, plus inline emphasis
|
|
122
|
+
(`*bold*` `/italic/` `_underline_` `~strike~` `=highlight=`, braced
|
|
123
|
+
`{^sup^}` `{,sub,}`),
|
|
124
|
+
code, links, images, spans, attributes, footnotes, math (`` $`x`$ ``),
|
|
125
|
+
CriticMarkup (`{+ins+}` `{-del-}`), mentions, tags and emoji.
|
|
126
|
+
|
|
127
|
+
### Prism
|
|
128
|
+
|
|
129
|
+
The grammar registers itself against the global `Prism`, so `Prism` must be
|
|
130
|
+
global before the grammar module runs. Because static `import` statements are
|
|
131
|
+
hoisted (they all evaluate before any top-level assignment), load the grammar
|
|
132
|
+
with a dynamic `import` after assigning `globalThis.Prism`:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
import Prism from 'prismjs'
|
|
136
|
+
|
|
137
|
+
globalThis.Prism = Prism // grammar reads the global Prism
|
|
138
|
+
await import('@markup-carve/carve-grammars/prism/carve.js') // registers Prism.languages.carve
|
|
139
|
+
|
|
140
|
+
const html = Prism.highlight(source, Prism.languages.carve, 'carve')
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
In the browser, load `prismjs` first (it sets the global `Prism`), then load
|
|
144
|
+
`@markup-carve/carve-grammars/prism/carve.js`.
|
|
145
|
+
|
|
146
|
+
### highlight.js
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
import hljs from 'highlight.js'
|
|
150
|
+
import carve from '@markup-carve/carve-grammars/highlightjs/carve.js'
|
|
151
|
+
|
|
152
|
+
hljs.registerLanguage('carve', carve)
|
|
153
|
+
const { value } = hljs.highlight(source, { language: 'carve' })
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Loaded as a classic `<script>` after highlight.js, it self-registers against
|
|
157
|
+
the global `hljs`:
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<script src="highlight.min.js"></script>
|
|
161
|
+
<script src="node_modules/@markup-carve/carve-grammars/highlightjs/carve.js"></script>
|
|
162
|
+
<script>hljs.highlightAll();</script>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Shiki / VitePress
|
|
166
|
+
|
|
167
|
+
`@markup-carve/carve-grammars/shiki` is the shared kit every Carve docs site uses, so
|
|
168
|
+
highlighting stays identical across them: the TextMate grammar, GitHub
|
|
169
|
+
light/dark themes extended with Carve scope colors, and a transformer + CSS
|
|
170
|
+
pair that bridges what Shiki's HTML emitter cannot express (strikethrough,
|
|
171
|
+
sub/superscript positioning, highlight background).
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// .vitepress/config.ts
|
|
175
|
+
import { defineConfig } from 'vitepress'
|
|
176
|
+
import { carveMarkdown } from '@markup-carve/carve-grammars/shiki'
|
|
177
|
+
|
|
178
|
+
export default defineConfig({
|
|
179
|
+
markdown: {
|
|
180
|
+
...carveMarkdown(),
|
|
181
|
+
// carveMarkdown({ light, dark, languages }) to override base themes
|
|
182
|
+
// or register extra grammars
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// .vitepress/theme/index.ts
|
|
189
|
+
import '@markup-carve/carve-grammars/shiki/carve.css'
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Named exports for other setups: `carveGrammar`, `carveLightExtras` /
|
|
193
|
+
`carveDarkExtras`, `carveLightTheme` / `carveDarkTheme`, `extendTheme`,
|
|
194
|
+
`carveStylingTransformer`.
|
|
195
|
+
|
|
196
|
+
## API
|
|
197
|
+
|
|
198
|
+
- `serializeToCarve(doc)` - serialize an `editor.getJSON()` document to Carve markup.
|
|
199
|
+
- `escapeCarve(text)` - contextually escape literal Carve syntax in a plain-text run so it round-trips as text (used internally by `serializeToCarve`).
|
|
200
|
+
- `CarveKit` - the bundled Tiptap extension set.
|
|
201
|
+
- Individual extensions: `CarveInsert`, `CarveDelete`, `CarveDiv`, `CarveSpan`, `CarveFootnote`, `CarveFootnoteDefinition`, `CarveMath`, `CarveEmbed`, `CarveAbbreviation`, `CarveDefinitionList`.
|
|
202
|
+
|
|
203
|
+
## Attributes, math and footnotes
|
|
204
|
+
|
|
205
|
+
- **Attributes** - spans, headings and images serialize an `id` and `class`
|
|
206
|
+
(and any extra non-structural attrs) as a `{#id .class key="val"}` block, e.g.
|
|
207
|
+
`[text]{#me .note}`, `{.wide}`. Inline attrs trail their target;
|
|
208
|
+
block attrs (headings) sit on the **preceding** line (strict djot), e.g.
|
|
209
|
+
`{#slug}` then `# Title`.
|
|
210
|
+
- **Math** - `CarveMath` (inline atom) serializes to `` $`x`$ `` and, with
|
|
211
|
+
`display: true`, `` $$`x`$$ ``.
|
|
212
|
+
- **Footnotes** - `CarveFootnote` is the inline `[^label]` reference;
|
|
213
|
+
`CarveFootnoteDefinition` is the matching body block, serialized as
|
|
214
|
+
`[^label]: body`.
|
|
215
|
+
|
|
216
|
+
## Tests
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npm test
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The suite holds all three grammars to one source of truth: the shared corpus
|
|
223
|
+
from the [`markup-carve/carve`](https://github.com/markup-carve/carve) spec,
|
|
224
|
+
vendored as the `spec/` git submodule (`git submodule update --init`).
|
|
225
|
+
|
|
226
|
+
- `npm run test:coverage` - the coverage matrix. Each grammar (prism,
|
|
227
|
+
highlightjs, tiptap) declares a covered-category set and a skip set (with a
|
|
228
|
+
reason per skip); the test fails if the two do not partition every corpus
|
|
229
|
+
category, so a new spec category forces a deliberate decision.
|
|
230
|
+
- `npm run test:snapshot` - golden token snapshots. Each covered `.crv` is
|
|
231
|
+
tokenized with Prism's and highlight.js's own tokenizers and the token stream
|
|
232
|
+
(type + text) is compared against a committed golden in `tests/snapshots/`.
|
|
233
|
+
Refresh intended changes with `npm run snapshots:update`.
|
|
234
|
+
- `npm run test:roundtrip` - the Tiptap serializer round-trip. Each covered
|
|
235
|
+
`.crv` runs `parse -> ProseMirror JSON -> serializeToCarve -> parse` and the
|
|
236
|
+
two parsed ASTs must be identical, catching serializer drift. Categories the
|
|
237
|
+
serializer cannot represent are skipped with a reason.
|
|
238
|
+
|
|
239
|
+
`npm test` runs all of the above plus the structural grammar and serializer
|
|
240
|
+
unit tests. CI runs the same on Node 18, 20 and 22.
|