@markup-carve/carve-grammars 0.1.7 → 0.1.8
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/README.md +10 -0
- package/package.json +1 -1
- package/tiptap/carve-editor.js +70 -0
- package/tiptap/carve-kit.js +112 -8
- package/tiptap/carve-to-pm.js +17 -6
- package/tiptap/editor.css +433 -1
- package/tiptap/extensions/carve-abbreviation-definition.js +7 -0
- package/tiptap/extensions/carve-citation.js +14 -0
- package/tiptap/extensions/carve-comment.js +7 -3
- package/tiptap/extensions/carve-crossref.js +13 -0
- package/tiptap/extensions/carve-embed.js +35 -4
- package/tiptap/extensions/carve-footnote.js +18 -0
- package/tiptap/extensions/carve-frontmatter.js +122 -0
- package/tiptap/extensions/carve-link-ref-def.js +7 -0
- package/tiptap/extensions/carve-literal.js +4 -3
- package/tiptap/extensions/carve-math.js +12 -0
- package/tiptap/extensions/carve-mention.js +9 -0
- package/tiptap/extensions/carve-raw-inline.js +4 -3
- package/tiptap/extensions/carve-substitution.js +11 -0
- package/tiptap/extensions/carve-symbol.js +8 -0
- package/tiptap/extensions/carve-unsupported-inline.js +3 -0
- package/tiptap/extensions/carve-unsupported.js +3 -0
- package/tiptap/extensions/editable-atom-view.js +356 -0
- package/tiptap/extensions/panel-bar.js +24 -3
- package/tiptap/schema-map.json +6 -5
- package/tiptap/serializer.js +13 -6
- package/tiptap/wire-fixtures.json +16 -7
package/README.md
CHANGED
|
@@ -259,6 +259,16 @@ available as the `editor` CSS part (`carve-editor::part(editor)`). Tiptap stays
|
|
|
259
259
|
an implementation detail of the element, although its peer packages must be
|
|
260
260
|
installed with `carve-grammars`.
|
|
261
261
|
|
|
262
|
+
Document front matter appears as a collapsed **Document metadata** card. Expand
|
|
263
|
+
it to edit the common `title`, `lang`, `author`, and `description` fields, or use
|
|
264
|
+
the raw YAML/TOML field for custom metadata. Both paths update the document
|
|
265
|
+
through the editor, so they participate in undo/redo and preserve unknown keys.
|
|
266
|
+
Unsupported-source atoms use the same compact/editable pattern for their exact
|
|
267
|
+
Carve payload. Inline footnotes, cross-references, and citations open target
|
|
268
|
+
pickers, while abbreviation and link-reference definitions use collapsible
|
|
269
|
+
forms. Import `@markup-carve/carve-grammars/tiptap/editor.css` when mounting
|
|
270
|
+
`CarveKit` directly to receive the associated editor chrome and table styling.
|
|
271
|
+
|
|
262
272
|
## Syntax highlighting
|
|
263
273
|
|
|
264
274
|
Render Carve source as highlighted HTML on the web. Both grammars cover the full
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markup-carve/carve-grammars",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Grammars for the Carve markup language: Tiptap editor kit + serializer, plus Prism, highlight.js and TextMate syntax-highlighting grammars",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "tiptap/index.js",
|
package/tiptap/carve-editor.js
CHANGED
|
@@ -32,6 +32,73 @@ export class CarveEditorElement extends HTMLElementBase {
|
|
|
32
32
|
[part="editor"] { min-height: 8rem; }
|
|
33
33
|
.ProseMirror { min-height: inherit; outline: none; }
|
|
34
34
|
.ProseMirror:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
|
|
35
|
+
.carve-frontmatter-card { border: 1px solid #d5d9df; border-radius: 4px; margin-block-end: 1rem; }
|
|
36
|
+
.carve-frontmatter-summary { width: 100%; border: 0; background: #f4f5f7; color: inherit; cursor: pointer; font: inherit; font-weight: 600; padding: .65rem .8rem; text-align: start; }
|
|
37
|
+
.carve-frontmatter-summary::before { content: '▸'; display: inline-block; margin-inline-end: .5rem; }
|
|
38
|
+
.carve-frontmatter-summary[aria-expanded="true"]::before { transform: rotate(90deg); }
|
|
39
|
+
.carve-frontmatter-body { padding: .8rem; }
|
|
40
|
+
.carve-frontmatter-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .75rem; }
|
|
41
|
+
.carve-frontmatter-card label { display: grid; gap: .25rem; font-size: .875rem; font-weight: 600; }
|
|
42
|
+
.carve-frontmatter-card input, .carve-frontmatter-card textarea { box-sizing: border-box; width: 100%; border: 1px solid #b9c0ca; border-radius: 3px; font: inherit; padding: .5rem; }
|
|
43
|
+
.carve-frontmatter-raw { margin-block-start: .75rem; }
|
|
44
|
+
.carve-frontmatter-raw textarea { min-height: 7rem; font-family: monospace; font-weight: 400; }
|
|
45
|
+
.ProseMirror table { width: 100%; border-collapse: collapse; outline: 1px solid #d5d9df; }
|
|
46
|
+
.ProseMirror th, .ProseMirror td { border: 1px solid #d5d9df; padding: .5rem; vertical-align: top; }
|
|
47
|
+
.ProseMirror th { background: #f4f5f7; font-weight: 700; }
|
|
48
|
+
.ProseMirror a { color: #1769c2; text-underline-offset: .14em; }
|
|
49
|
+
.ProseMirror blockquote { margin: 0 0 1rem; padding-inline-start: 1rem; border-inline-start: 3px solid #c8ced7; color: #5a626d; }
|
|
50
|
+
.ProseMirror blockquote:focus-within { border-inline-start-color: #1769c2; }
|
|
51
|
+
.ProseMirror blockquote > :first-child { margin-block-start: 0; }
|
|
52
|
+
.ProseMirror blockquote > :last-child { margin-block-end: 0; }
|
|
53
|
+
.ProseMirror ul[data-type="taskList"] { list-style: none; padding-inline-start: 0; }
|
|
54
|
+
.ProseMirror ul[data-type="taskList"] ul[data-type="taskList"] { padding-inline-start: 1.5rem; }
|
|
55
|
+
.ProseMirror ul[data-type="taskList"] > li[data-checked] { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; column-gap: .5rem; }
|
|
56
|
+
.ProseMirror ul[data-type="taskList"] > li[data-checked] > label { margin-block-start: .2em; line-height: inherit; }
|
|
57
|
+
.ProseMirror ul[data-type="taskList"] > li[data-checked] > div > :first-child { margin-block-start: 0; }
|
|
58
|
+
.ProseMirror ul[data-type="taskList"] > li[data-checked] > div > :last-child { margin-block-end: 0; }
|
|
59
|
+
.ProseMirror pre[data-carve-comment] { margin-block: .75rem; border: 1px dashed #7a828c; border-radius: 4px; background: #f4f5f7; color: #5a626d; padding: .55rem .75rem .65rem; white-space: pre-wrap; }
|
|
60
|
+
.ProseMirror pre[data-carve-comment]::before { content: "Comment"; display: block; margin-block-end: .35rem; color: #0e4459; font: 700 .7rem/1.2 system-ui, sans-serif; letter-spacing: .08em; text-transform: uppercase; }
|
|
61
|
+
.ProseMirror span[data-carve-comment-inline] { border: 1px dashed #7a828c; border-radius: 4px; background: #e6f0f4; color: #5a626d; padding: .05em .3em; }
|
|
62
|
+
.ProseMirror span[data-carve-comment-inline]::before { content: "Comment: "; color: #0e4459; font-size: .78em; font-weight: 700; }
|
|
63
|
+
.ProseMirror [data-carve-extension], .ProseMirror [data-carve-empty-mark-node] { border-radius: 3px; background: #f4f5f7; padding: .05em .3em; }
|
|
64
|
+
.ProseMirror [data-carve-extension]::before { content: ':' attr(data-carve-extension) '['; color: #0e4459; font: 600 .78em/1 monospace; margin-inline-end: .2em; }
|
|
65
|
+
.ProseMirror [data-carve-extension]::after { content: ']'; color: #0e4459; font: 600 .78em/1 monospace; margin-inline-start: .2em; }
|
|
66
|
+
.ProseMirror [data-carve-empty-mark-node]::before { content: 'Empty ' attr(data-carve-empty-mark); color: #5a626d; font: 600 .78em/1 system-ui, sans-serif; }
|
|
67
|
+
.ProseMirror [data-carve-citation-definition], .ProseMirror .carve-footnote-definition, .ProseMirror figcaption[data-carve-caption] { border-inline-start: 3px solid #c8ced7; padding-inline-start: .75rem; }
|
|
68
|
+
.ProseMirror [data-carve-citation-definition]::before, .ProseMirror .carve-footnote-definition::before, .ProseMirror figcaption[data-carve-caption]::before { display: block; color: #5a626d; font: 700 .68rem/1.3 system-ui, sans-serif; letter-spacing: .06em; text-transform: uppercase; }
|
|
69
|
+
.ProseMirror [data-carve-citation-definition]::before { content: 'Citation · ' attr(key); }
|
|
70
|
+
.ProseMirror .carve-footnote-definition::before { content: 'Footnote · ' attr(data-footnote-label); }
|
|
71
|
+
.ProseMirror figcaption[data-carve-caption]::before { content: 'Caption'; }
|
|
72
|
+
.ProseMirror .critic-comment { background: #fff6dc; text-decoration: underline dotted #9a6700; text-underline-offset: .2em; }
|
|
73
|
+
.carve-code-block { position: relative; margin-block: .75rem; border: 1px solid #d5d9df; border-radius: 4px; overflow: hidden; }
|
|
74
|
+
.carve-code-block-chrome { display: flex; align-items: center; justify-content: flex-end; padding: .375rem .5rem; border-block-start: 1px solid #d5d9df; background: white; }
|
|
75
|
+
.carve-code-block-chrome > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
76
|
+
.carve-code-lang { max-width: 12rem; border: 1px solid #1769c2; border-radius: 3px; background: #1769c2; color: white; font: inherit; font-weight: 600; padding: .3rem .55rem; }
|
|
77
|
+
.carve-code-block > pre { margin: 0; border: 0; border-radius: 0; padding: var(--carve-code-padding, .75rem); }
|
|
78
|
+
.carve-inline-control, .carve-raw-atom-inline { position: relative; display: inline-flex; }
|
|
79
|
+
.carve-inline-control-trigger, .carve-raw-atom-summary { border: 1px solid #c8ced7; border-radius: 999px; background: #f4f5f7; color: inherit; cursor: pointer; font: inherit; padding: .1em .45em; }
|
|
80
|
+
.carve-inline-control-editor, .carve-raw-atom-inline .carve-raw-atom-editor { position: absolute; z-index: 20; inset-block-start: 100%; inset-inline-start: 0; display: grid; grid-template-columns: 1fr auto; gap: .4rem; min-width: min(22rem, 80vw); padding: .5rem; border: 1px solid #c8ced7; background: white; }
|
|
81
|
+
.carve-inline-control-editor[hidden], .carve-raw-atom-editor[hidden] { display: none; }
|
|
82
|
+
.carve-inline-control-editor > input, .carve-inline-choices { grid-column: 1 / -1; }
|
|
83
|
+
.carve-inline-control-editor > .carve-control-primary { justify-self: end; }
|
|
84
|
+
.carve-inline-fields-editor > label { grid-column: 1 / -1; display: grid; gap: .2rem; color: #5a626d; font-size: .8rem; }
|
|
85
|
+
.carve-inline-fields-editor textarea { min-height: 4.5rem; resize: vertical; }
|
|
86
|
+
.carve-inline-fields-editor input, .carve-inline-fields-editor textarea { width: 100%; min-width: 0; font: inherit; }
|
|
87
|
+
.carve-inline-fields-editor input[type="checkbox"] { width: auto; justify-self: start; }
|
|
88
|
+
.carve-inline-field-actions { grid-column: 1 / -1; display: flex; justify-content: flex-end; gap: .4rem; }
|
|
89
|
+
.carve-embed-editor { display: grid; grid-template-columns: 1fr auto auto; gap: .4rem; padding: .6rem; border-block-start: 1px solid #c8ced7; background: white; }
|
|
90
|
+
.carve-embed-editor[hidden] { display: none; }
|
|
91
|
+
.carve-embed-editor label { grid-column: 1 / -1; display: grid; gap: .25rem; }
|
|
92
|
+
.carve-embed-editor input { width: 100%; min-width: 0; font: inherit; }
|
|
93
|
+
.carve-inline-choices { display: grid; max-height: 11rem; overflow-y: auto; border: 1px solid #d5d9df; border-radius: 3px; padding: .2rem; }
|
|
94
|
+
.carve-inline-choices[hidden] { display: none; }
|
|
95
|
+
.carve-inline-choice { width: 100%; border: 0; background: transparent; color: inherit; padding: .4rem .5rem; text-align: start; }
|
|
96
|
+
.carve-inline-choice:hover, .carve-inline-choice:focus-visible, .carve-inline-choice[aria-selected="true"] { background: #e6f0f4; color: #0e4459; }
|
|
97
|
+
.carve-definition-card, .carve-raw-atom:not(.carve-raw-atom-inline) { border: 1px solid #d5d9df; border-radius: 4px; margin-block: .75rem; }
|
|
98
|
+
.carve-definition-summary, .carve-raw-atom:not(.carve-raw-atom-inline) > .carve-raw-atom-summary { display: block; width: 100%; border: 0; border-radius: 0; text-align: start; font-weight: 600; padding: .6rem .75rem; }
|
|
99
|
+
.carve-definition-body, .carve-raw-atom:not(.carve-raw-atom-inline) > .carve-raw-atom-editor { padding: .75rem; }
|
|
100
|
+
.carve-definition-body label { display: grid; gap: .25rem; margin-block-end: .6rem; }
|
|
101
|
+
.carve-definition-body input, .carve-definition-body textarea, .carve-raw-atom-editor textarea { box-sizing: border-box; width: 100%; padding: .5rem; }
|
|
35
102
|
</style>
|
|
36
103
|
<div part="editor"></div>
|
|
37
104
|
`;
|
|
@@ -62,6 +129,9 @@ export class CarveEditorElement extends HTMLElementBase {
|
|
|
62
129
|
attributeChangedCallback(name) {
|
|
63
130
|
if (name === 'readonly' && this._editor) {
|
|
64
131
|
this._editor.setEditable(!this.hasAttribute('readonly'));
|
|
132
|
+
for (const control of this.shadowRoot.querySelectorAll('.carve-frontmatter-card input, .carve-frontmatter-card textarea, [data-carve-edit-control]')) {
|
|
133
|
+
control.disabled = this.hasAttribute('readonly');
|
|
134
|
+
}
|
|
65
135
|
}
|
|
66
136
|
}
|
|
67
137
|
|
package/tiptap/carve-kit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Extension } from '@tiptap/core';
|
|
2
|
+
import { Plugin } from '@tiptap/pm/state';
|
|
2
3
|
import StarterKit from '@tiptap/starter-kit';
|
|
3
4
|
import CodeBlock from '@tiptap/extension-code-block';
|
|
4
5
|
import Highlight from '@tiptap/extension-highlight';
|
|
@@ -67,6 +68,7 @@ import { CarveMention, CarveTag } from './extensions/carve-mention.js';
|
|
|
67
68
|
// shown even if it is not in this list.
|
|
68
69
|
const CODE_LANGS = [
|
|
69
70
|
{ value: '', label: 'Plain text' },
|
|
71
|
+
{ value: 'carve', label: 'Carve' },
|
|
70
72
|
{ value: 'php', label: 'PHP' },
|
|
71
73
|
{ value: 'javascript', label: 'JavaScript' },
|
|
72
74
|
{ value: 'typescript', label: 'TypeScript' },
|
|
@@ -147,12 +149,68 @@ function authoredClasses(element) {
|
|
|
147
149
|
return kept.length ? kept.join(' ') : null;
|
|
148
150
|
}
|
|
149
151
|
|
|
152
|
+
const LEGACY_INLINE_CONTENT_NODES = new Set([
|
|
153
|
+
'carveCommentInline',
|
|
154
|
+
'carveLiteral',
|
|
155
|
+
'carveRawInline',
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
// These nodes used to store their editable payload in attrs.content. Keep old
|
|
159
|
+
// persisted ProseMirror JSON usable by promoting that attribute to child text
|
|
160
|
+
// as soon as it enters an editor. Applying replacements from right to left
|
|
161
|
+
// keeps every collected position valid as leaf nodes grow children.
|
|
162
|
+
function legacyInlineContentTransaction(state) {
|
|
163
|
+
const replacements = [];
|
|
164
|
+
state.doc.descendants((node, pos) => {
|
|
165
|
+
const legacyContent = node.attrs?.content;
|
|
166
|
+
if (LEGACY_INLINE_CONTENT_NODES.has(node.type.name)
|
|
167
|
+
&& node.childCount === 0
|
|
168
|
+
&& typeof legacyContent === 'string'
|
|
169
|
+
&& legacyContent.length > 0) {
|
|
170
|
+
replacements.push({ node, pos, legacyContent });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
if (replacements.length === 0) return null;
|
|
174
|
+
|
|
175
|
+
const transaction = state.tr;
|
|
176
|
+
for (const { node, pos, legacyContent } of replacements.reverse()) {
|
|
177
|
+
transaction.replaceWith(
|
|
178
|
+
pos,
|
|
179
|
+
pos + node.nodeSize,
|
|
180
|
+
node.type.create(
|
|
181
|
+
{ ...node.attrs, content: null },
|
|
182
|
+
state.schema.text(legacyContent),
|
|
183
|
+
node.marks,
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
transaction.setMeta('addToHistory', false);
|
|
188
|
+
transaction.setMeta('carveLegacyInlineContentMigration', true);
|
|
189
|
+
return transaction;
|
|
190
|
+
}
|
|
191
|
+
|
|
150
192
|
export const CarveKit = Extension.create({
|
|
151
193
|
name: 'carveKit',
|
|
152
194
|
|
|
153
195
|
addExtensions() {
|
|
154
196
|
const extensions = [];
|
|
155
197
|
|
|
198
|
+
extensions.push(Extension.create({
|
|
199
|
+
name: 'carveLegacyInlineContentMigration',
|
|
200
|
+
onCreate() {
|
|
201
|
+
const transaction = legacyInlineContentTransaction(this.editor.state);
|
|
202
|
+
if (transaction) this.editor.view.dispatch(transaction);
|
|
203
|
+
},
|
|
204
|
+
addProseMirrorPlugins() {
|
|
205
|
+
return [new Plugin({
|
|
206
|
+
appendTransaction(transactions, _oldState, newState) {
|
|
207
|
+
if (transactions.some(transaction => transaction.getMeta('carveLegacyInlineContentMigration'))) return null;
|
|
208
|
+
return legacyInlineContentTransaction(newState);
|
|
209
|
+
},
|
|
210
|
+
})];
|
|
211
|
+
},
|
|
212
|
+
}));
|
|
213
|
+
|
|
156
214
|
// Attributes on ordinary block nodes are part of the Carve document,
|
|
157
215
|
// but the stock Tiptap schema does not declare them. ProseMirror drops
|
|
158
216
|
// undeclared fields while mounting JSON, so retain the common Carve
|
|
@@ -262,9 +320,11 @@ export const CarveKit = Extension.create({
|
|
|
262
320
|
};
|
|
263
321
|
},
|
|
264
322
|
|
|
265
|
-
//
|
|
266
|
-
// code
|
|
267
|
-
//
|
|
323
|
+
// Language toolbar below every code block. Keeping the control
|
|
324
|
+
// outside <pre>/<code> means it never consumes the first line's
|
|
325
|
+
// horizontal space or becomes part of the editable content,
|
|
326
|
+
// while placing it after the source keeps metadata secondary.
|
|
327
|
+
// Disable with
|
|
268
328
|
// CarveKit.configure({ codeBlock: { languagePicker: false } }).
|
|
269
329
|
addNodeView() {
|
|
270
330
|
if (this.options.languagePicker === false) {
|
|
@@ -272,6 +332,8 @@ export const CarveKit = Extension.create({
|
|
|
272
332
|
}
|
|
273
333
|
return ({ node, editor, getPos }) => {
|
|
274
334
|
let current = node;
|
|
335
|
+
const dom = document.createElement('div');
|
|
336
|
+
dom.className = 'carve-code-block';
|
|
275
337
|
const pre = document.createElement('pre');
|
|
276
338
|
if (node.attrs.carveLanguageRaw) {
|
|
277
339
|
pre.setAttribute('data-language-raw', node.attrs.carveLanguageRaw);
|
|
@@ -281,6 +343,11 @@ export const CarveKit = Extension.create({
|
|
|
281
343
|
select.className = 'carve-code-lang';
|
|
282
344
|
select.contentEditable = 'false';
|
|
283
345
|
select.setAttribute('aria-label', 'Code language');
|
|
346
|
+
const chrome = document.createElement('div');
|
|
347
|
+
chrome.className = 'carve-code-block-chrome';
|
|
348
|
+
chrome.contentEditable = 'false';
|
|
349
|
+
const label = document.createElement('span');
|
|
350
|
+
label.textContent = 'Language';
|
|
284
351
|
const fill = (lang) => {
|
|
285
352
|
select.innerHTML = '';
|
|
286
353
|
const opts = CODE_LANGS.slice();
|
|
@@ -318,11 +385,12 @@ export const CarveKit = Extension.create({
|
|
|
318
385
|
code.className = lang ? `language-${lang}` : '';
|
|
319
386
|
};
|
|
320
387
|
applyLangClass(node.attrs.language || '');
|
|
321
|
-
pre.appendChild(select);
|
|
322
388
|
pre.appendChild(code);
|
|
389
|
+
chrome.append(label, select);
|
|
390
|
+
dom.append(pre, chrome);
|
|
323
391
|
|
|
324
392
|
return {
|
|
325
|
-
dom
|
|
393
|
+
dom,
|
|
326
394
|
contentDOM: code,
|
|
327
395
|
update: (updated) => {
|
|
328
396
|
if (updated.type !== current.type) {
|
|
@@ -336,8 +404,8 @@ export const CarveKit = Extension.create({
|
|
|
336
404
|
return true;
|
|
337
405
|
},
|
|
338
406
|
// The <select> is chrome, not editable content.
|
|
339
|
-
ignoreMutation: (m) =>
|
|
340
|
-
stopEvent: (e) =>
|
|
407
|
+
ignoreMutation: (m) => chrome.contains(m.target),
|
|
408
|
+
stopEvent: (e) => chrome.contains(e.target),
|
|
341
409
|
};
|
|
342
410
|
};
|
|
343
411
|
},
|
|
@@ -396,6 +464,25 @@ export const CarveKit = Extension.create({
|
|
|
396
464
|
carveBareMarker: { default: null },
|
|
397
465
|
};
|
|
398
466
|
},
|
|
467
|
+
addCommands() {
|
|
468
|
+
return {
|
|
469
|
+
...this.parent?.(),
|
|
470
|
+
// A list CREATED in Tiptap has no authored marker to
|
|
471
|
+
// preserve. Prefer Carve's constant-width automatic
|
|
472
|
+
// marker without changing the schema default: keeping
|
|
473
|
+
// that nullable preserves older persisted PM JSON.
|
|
474
|
+
toggleOrderedList: () => ({ chain, editor }) => {
|
|
475
|
+
const wasActive = editor.isActive(this.name);
|
|
476
|
+
const command = chain().toggleList(
|
|
477
|
+
this.name, this.options.itemTypeName, this.options.keepMarks,
|
|
478
|
+
);
|
|
479
|
+
if (wasActive) return command.run();
|
|
480
|
+
return command.updateAttributes(this.name, {
|
|
481
|
+
carveBareMarker: true, carveDelim: '.',
|
|
482
|
+
}).run();
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
},
|
|
399
486
|
});
|
|
400
487
|
extensions.push(CustomOrderedList.configure(this.options.orderedList ?? {}));
|
|
401
488
|
}
|
|
@@ -532,9 +619,26 @@ export const CarveKit = Extension.create({
|
|
|
532
619
|
resizable: true,
|
|
533
620
|
...this.options.table,
|
|
534
621
|
}));
|
|
622
|
+
const tableAlign = {
|
|
623
|
+
default: null,
|
|
624
|
+
parseHTML: element => !element.hasAttribute('data-carve-inherited-align')
|
|
625
|
+
&& ['left', 'center', 'right'].includes(element.style.textAlign)
|
|
626
|
+
? element.style.textAlign : null,
|
|
627
|
+
renderHTML: attrs => ['left', 'center', 'right'].includes(attrs.textAlign)
|
|
628
|
+
? { style: `text-align: ${attrs.textAlign}` } : {},
|
|
629
|
+
};
|
|
630
|
+
const inheritedTableAlign = {
|
|
631
|
+
default: null,
|
|
632
|
+
parseHTML: () => null,
|
|
633
|
+
renderHTML: attrs => !attrs.textAlign && ['left', 'center', 'right'].includes(attrs.carveInheritedTextAlign)
|
|
634
|
+
? {
|
|
635
|
+
style: `text-align: ${attrs.carveInheritedTextAlign}`,
|
|
636
|
+
'data-carve-inherited-align': attrs.carveInheritedTextAlign,
|
|
637
|
+
} : {},
|
|
638
|
+
};
|
|
535
639
|
const tableAttrs = {
|
|
536
640
|
id: { default: null }, class: { default: null }, carveKeyValues: { default: null },
|
|
537
|
-
...attributeOrderSlot(), textAlign:
|
|
641
|
+
...attributeOrderSlot(), textAlign: tableAlign, carveInheritedTextAlign: inheritedTableAlign,
|
|
538
642
|
};
|
|
539
643
|
const CustomTableRow = TableRow.extend({ addAttributes() { return { ...this.parent?.(), ...tableAttrs }; } });
|
|
540
644
|
const CustomTableCell = TableCell.extend({ addAttributes() { return { ...this.parent?.(), ...tableAttrs }; } });
|
package/tiptap/carve-to-pm.js
CHANGED
|
@@ -282,7 +282,7 @@ function unsupported(type, node, ctx) {
|
|
|
282
282
|
if (ctx?.unsupported === 'preserve') {
|
|
283
283
|
const carveSource = sourceFor(node, ctx);
|
|
284
284
|
if (carveSource) {
|
|
285
|
-
record(ctx, 'preserved', type, 'kept as exact source in
|
|
285
|
+
record(ctx, 'preserved', type, 'kept as exact source in an editable carveUnsupported atom');
|
|
286
286
|
|
|
287
287
|
// The TYPE the atom stands in for, not only its source. Without it
|
|
288
288
|
// a caller can see that something was preserved and never learn
|
|
@@ -493,7 +493,10 @@ function convertBlock(node, ctx) {
|
|
|
493
493
|
// Dropping it rewrote every alpha and roman list as `1.`.
|
|
494
494
|
if (node.olType) listNode.attrs.carveOlType = node.olType;
|
|
495
495
|
if (node.delim) listNode.attrs.carveDelim = node.delim;
|
|
496
|
-
|
|
496
|
+
// Spell this out in both directions. The schema default is
|
|
497
|
+
// `true` for newly authored Tiptap lists, while an imported
|
|
498
|
+
// explicit `1.` list must override that default with `false`.
|
|
499
|
+
listNode.attrs.carveBareMarker = node.bareMarker === true;
|
|
497
500
|
}
|
|
498
501
|
return listNode;
|
|
499
502
|
}
|
|
@@ -774,6 +777,11 @@ function convertTable(node, ctx) {
|
|
|
774
777
|
|
|
775
778
|
function convertTableCore(node, ctx) {
|
|
776
779
|
let previousGrid = [];
|
|
780
|
+
const inheritedColumnAligns = (node.columns || []).map(column => column?.align || null);
|
|
781
|
+
const headingRow = (node.rows || []).find(row => (row.cells || []).some(cell => cell.header));
|
|
782
|
+
(headingRow?.cells || []).forEach((cell, col) => {
|
|
783
|
+
if (cell.header && cell.align) inheritedColumnAligns[col] = cell.align;
|
|
784
|
+
});
|
|
777
785
|
const rows = (node.rows || []).map((row) => {
|
|
778
786
|
const cells = [];
|
|
779
787
|
const grid = [];
|
|
@@ -805,6 +813,7 @@ function convertTableCore(node, ctx) {
|
|
|
805
813
|
}
|
|
806
814
|
const attrs = { ...(convertAttrs(cell.attrs) || {}) };
|
|
807
815
|
if (cell.align) attrs.textAlign = cell.align;
|
|
816
|
+
else if (inheritedColumnAligns[col]) attrs.carveInheritedTextAlign = inheritedColumnAligns[col];
|
|
808
817
|
const converted = {
|
|
809
818
|
type: cell.header ? 'tableHeader' : 'tableCell',
|
|
810
819
|
attrs,
|
|
@@ -840,7 +849,7 @@ function convertInline(nodes, ctx) {
|
|
|
840
849
|
const carveSource = sourceFor(node, ctx);
|
|
841
850
|
if (carveSource) {
|
|
842
851
|
record(ctx, 'preserved', error.nodeType || node.type,
|
|
843
|
-
'kept as exact source in
|
|
852
|
+
'kept as exact source in an editable carveUnsupportedInline atom');
|
|
844
853
|
out.push(unsupportedInlineSource(carveSource, error.nodeType || node.type));
|
|
845
854
|
continue;
|
|
846
855
|
}
|
|
@@ -1097,7 +1106,8 @@ function convertInlineNode(node, marks, ctx) {
|
|
|
1097
1106
|
case 'literal_inline':
|
|
1098
1107
|
return [{
|
|
1099
1108
|
type: 'carveLiteral',
|
|
1100
|
-
attrs
|
|
1109
|
+
...(convertAttrs(node.attrs) ? { attrs: convertAttrs(node.attrs) } : {}),
|
|
1110
|
+
content: node.content ? [{ type: 'text', text: node.content }] : [],
|
|
1101
1111
|
...(marks.length ? { marks } : {}),
|
|
1102
1112
|
}];
|
|
1103
1113
|
|
|
@@ -1117,9 +1127,9 @@ function convertInlineNode(node, marks, ctx) {
|
|
|
1117
1127
|
type: 'carveRawInline',
|
|
1118
1128
|
attrs: {
|
|
1119
1129
|
format: node.format || '',
|
|
1120
|
-
content: node.content || '',
|
|
1121
1130
|
...(convertAttrs(node.attrs) || {}),
|
|
1122
1131
|
},
|
|
1132
|
+
content: node.content ? [{ type: 'text', text: node.content }] : [],
|
|
1123
1133
|
...(marks.length ? { marks } : {}),
|
|
1124
1134
|
}];
|
|
1125
1135
|
|
|
@@ -1165,7 +1175,8 @@ function convertInlineNode(node, marks, ctx) {
|
|
|
1165
1175
|
case 'comment':
|
|
1166
1176
|
return [{
|
|
1167
1177
|
type: 'carveCommentInline',
|
|
1168
|
-
attrs: {
|
|
1178
|
+
attrs: { delimited: Boolean(node.delimited) },
|
|
1179
|
+
content: node.content ? [{ type: 'text', text: node.content }] : [],
|
|
1169
1180
|
}];
|
|
1170
1181
|
|
|
1171
1182
|
case 'abbreviation':
|