testomatio-editor-blocks 0.4.79 → 0.4.82
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/package/editor/autoLink.d.ts +45 -0
- package/package/editor/autoLink.js +199 -0
- package/package/editor/blocks/exampleMarker.d.ts +19 -0
- package/package/editor/blocks/exampleMarker.js +12 -0
- package/package/editor/customMarkdownConverter.js +22 -0
- package/package/editor/customSchema.d.ts +12 -0
- package/package/editor/customSchema.js +2 -0
- package/package/editor/exampleTableHighlight.d.ts +46 -0
- package/package/editor/exampleTableHighlight.js +131 -0
- package/package/index.d.ts +2 -0
- package/package/index.js +2 -0
- package/package/styles.css +65 -0
- package/package.json +1 -1
- package/src/App.tsx +33 -6
- package/src/editor/autoLink.test.ts +111 -0
- package/src/editor/autoLink.ts +248 -0
- package/src/editor/blocks/exampleMarker.tsx +24 -0
- package/src/editor/customMarkdownConverter.test.ts +28 -0
- package/src/editor/customMarkdownConverter.ts +28 -0
- package/src/editor/customSchema.tsx +2 -0
- package/src/editor/exampleTableHighlight.test.ts +66 -0
- package/src/editor/exampleTableHighlight.ts +148 -0
- package/src/editor/styles.css +65 -0
- package/src/index.ts +13 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { BlockNoteExtension } from "@blocknote/core";
|
|
2
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
3
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
4
|
+
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* An "example table" is a data table that belongs to a testomat.io `<!-- example -->`
|
|
8
|
+
* marker: it sits between that marker and the start of the next test — i.e. the next
|
|
9
|
+
* `testMeta` block (`<!-- test ... -->` / `<!-- suite ... -->`) — or the end of the
|
|
10
|
+
* document. Such tables are painted with a gray cell background so they read as the
|
|
11
|
+
* marker's data table.
|
|
12
|
+
*
|
|
13
|
+
* Given the block content-type names in document order, returns a boolean[] where
|
|
14
|
+
* entry `i` is `true` iff a `"table"` at index `i` falls inside an example region.
|
|
15
|
+
* `exampleMarker` opens a region; `testMeta` closes it; everything else (headings,
|
|
16
|
+
* paragraphs, …) leaves the region unchanged.
|
|
17
|
+
*
|
|
18
|
+
* Pure and DOM-free so it can be unit-tested directly.
|
|
19
|
+
*/
|
|
20
|
+
export function markExampleTables(types: readonly string[]): boolean[] {
|
|
21
|
+
const flags: boolean[] = [];
|
|
22
|
+
let active = false;
|
|
23
|
+
for (const name of types) {
|
|
24
|
+
if (name === "exampleMarker") {
|
|
25
|
+
active = true;
|
|
26
|
+
flags.push(false);
|
|
27
|
+
} else if (name === "testMeta") {
|
|
28
|
+
active = false;
|
|
29
|
+
flags.push(false);
|
|
30
|
+
} else {
|
|
31
|
+
flags.push(name === "table" && active);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return flags;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Example tables only occur in a *suite* document — one that opens with a
|
|
39
|
+
* `<!-- suite ... -->` comment (a `testMeta` block whose `metaKind` is `"suite"`).
|
|
40
|
+
* Gating on the first block lets the plugin skip the whole scan for any other
|
|
41
|
+
* document (a lone test, plain notes, …) instead of walking it on every change.
|
|
42
|
+
*
|
|
43
|
+
* Pure so it can be unit-tested directly.
|
|
44
|
+
*/
|
|
45
|
+
export function isSuiteDocument(
|
|
46
|
+
firstBlockType: string | undefined,
|
|
47
|
+
firstBlockMetaKind: string | undefined,
|
|
48
|
+
): boolean {
|
|
49
|
+
return firstBlockType === "testMeta" && firstBlockMetaKind === "suite";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build node decorations that add `.bn-example-table` to every table in an example
|
|
54
|
+
* region. Tables are only *painted* — the document is untouched, so markdown
|
|
55
|
+
* serialization round-trips unchanged.
|
|
56
|
+
*
|
|
57
|
+
* The `<!-- example -->` marker, the metadata comment, and the table each become a
|
|
58
|
+
* top-level block whose content node is named by its block type (`exampleMarker`,
|
|
59
|
+
* `testMeta`, `table`), so a single document-order walk yields them directly.
|
|
60
|
+
*
|
|
61
|
+
* Nothing is highlighted unless the document is a suite (see `isSuiteDocument`).
|
|
62
|
+
*/
|
|
63
|
+
function buildExampleTableDecorations(doc: PMNode): DecorationSet {
|
|
64
|
+
const entries: { pos: number; size: number; name: string }[] = [];
|
|
65
|
+
let firstBlockType: string | undefined;
|
|
66
|
+
let firstBlockMetaKind: string | undefined;
|
|
67
|
+
|
|
68
|
+
doc.descendants((node, pos, parent) => {
|
|
69
|
+
// The first content node encountered under a blockContainer is the document's
|
|
70
|
+
// first block — capture its type/metaKind for the suite gate below.
|
|
71
|
+
if (firstBlockType === undefined && parent?.type.name === "blockContainer") {
|
|
72
|
+
firstBlockType = node.type.name;
|
|
73
|
+
const metaKind = node.attrs?.metaKind;
|
|
74
|
+
firstBlockMetaKind = typeof metaKind === "string" ? metaKind : undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const name = node.type.name;
|
|
78
|
+
if (name === "exampleMarker" || name === "testMeta" || name === "table") {
|
|
79
|
+
entries.push({ pos, size: node.nodeSize, name });
|
|
80
|
+
// No need to descend into table cells or the marker's (empty) internals.
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
// Descend through the blockGroup / blockContainer wrappers to reach content.
|
|
84
|
+
return undefined;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!isSuiteDocument(firstBlockType, firstBlockMetaKind)) {
|
|
88
|
+
return DecorationSet.empty;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const flags = markExampleTables(entries.map((entry) => entry.name));
|
|
92
|
+
const decorations = entries
|
|
93
|
+
.filter((_, index) => flags[index])
|
|
94
|
+
.map((entry) =>
|
|
95
|
+
Decoration.node(entry.pos, entry.pos + entry.size, {
|
|
96
|
+
class: "bn-example-table",
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return DecorationSet.create(doc, decorations);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const exampleTablePluginKey = new PluginKey<DecorationSet>("testomatioExampleTable");
|
|
104
|
+
|
|
105
|
+
function exampleTablePlugin(): Plugin<DecorationSet> {
|
|
106
|
+
return new Plugin<DecorationSet>({
|
|
107
|
+
key: exampleTablePluginKey,
|
|
108
|
+
state: {
|
|
109
|
+
init: (_config, state) => buildExampleTableDecorations(state.doc),
|
|
110
|
+
apply: (tr, value) =>
|
|
111
|
+
tr.docChanged ? buildExampleTableDecorations(tr.doc) : value,
|
|
112
|
+
},
|
|
113
|
+
props: {
|
|
114
|
+
decorations(state) {
|
|
115
|
+
return exampleTablePluginKey.getState(state);
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* BlockNote extension that grays the cells of the table following an
|
|
123
|
+
* `<!-- example -->` marker (bounded by the next test/suite comment or the end of
|
|
124
|
+
* the document).
|
|
125
|
+
*
|
|
126
|
+
* Editor extensions are supplied at editor-creation time and cannot be carried by
|
|
127
|
+
* the schema, so consumers must add this to their `useCreateBlockNote` call:
|
|
128
|
+
*
|
|
129
|
+
* ```ts
|
|
130
|
+
* useCreateBlockNote({
|
|
131
|
+
* schema: customSchema,
|
|
132
|
+
* extensions: [exampleTableHighlightExtension()],
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export class ExampleTableHighlightExtension extends BlockNoteExtension {
|
|
137
|
+
static key() {
|
|
138
|
+
return "exampleTableHighlight";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
constructor() {
|
|
142
|
+
super();
|
|
143
|
+
this.addProsemirrorPlugin(exampleTablePlugin());
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Factory for the `extensions` option of `useCreateBlockNote`. */
|
|
148
|
+
export const exampleTableHighlightExtension = () => new ExampleTableHighlightExtension();
|
package/src/editor/styles.css
CHANGED
|
@@ -771,6 +771,26 @@ html.dark .testomatio-editor [data-content-type="heading"] .bn-tag-badge {
|
|
|
771
771
|
border-color: rgba(255, 255, 255, 0.12);
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
+
/* ============================================
|
|
775
|
+
AUTO-LINKED URLS
|
|
776
|
+
Bare `http(s)://` URLs are painted as links by a ProseMirror inline
|
|
777
|
+
decoration (see src/editor/autoLink.ts). The text stays editable and the
|
|
778
|
+
markdown is untouched; only the styling and a Cmd/Ctrl+click handler are
|
|
779
|
+
added. Opening on click is wired in the plugin, not via `pointer-events`.
|
|
780
|
+
============================================ */
|
|
781
|
+
.testomatio-editor .bn-auto-link {
|
|
782
|
+
color: var(--color-primary-700);
|
|
783
|
+
text-decoration: underline;
|
|
784
|
+
text-underline-offset: 2px;
|
|
785
|
+
cursor: pointer;
|
|
786
|
+
}
|
|
787
|
+
.testomatio-editor .bn-auto-link:hover {
|
|
788
|
+
color: var(--color-primary-600);
|
|
789
|
+
}
|
|
790
|
+
html.dark .testomatio-editor .bn-auto-link {
|
|
791
|
+
color: rgba(129, 140, 248, 1);
|
|
792
|
+
}
|
|
793
|
+
|
|
774
794
|
/* ============================================
|
|
775
795
|
TEST / SUITE METADATA BLOCK
|
|
776
796
|
Dimmed card for `<!-- test ... -->` / `<!-- suite ... -->` comments.
|
|
@@ -791,6 +811,37 @@ html.dark .testomatio-editor [data-content-type="heading"] .bn-tag-badge {
|
|
|
791
811
|
opacity: 0.5;
|
|
792
812
|
}
|
|
793
813
|
|
|
814
|
+
/* Compact marker for `<!-- example -->` — a cropped variant of the testMeta bar
|
|
815
|
+
(fit-content width, not full editor width) labelling the table below it. */
|
|
816
|
+
.bn-example-marker {
|
|
817
|
+
display: inline-flex;
|
|
818
|
+
align-items: center;
|
|
819
|
+
width: fit-content;
|
|
820
|
+
box-sizing: border-box;
|
|
821
|
+
padding: 4px 12px;
|
|
822
|
+
background: var(--bg-muted);
|
|
823
|
+
border-top: 3px solid var(--color-slate-400);
|
|
824
|
+
margin-top: 1rem;
|
|
825
|
+
opacity: 0.6;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.bn-example-marker__label {
|
|
829
|
+
font-size: 11px;
|
|
830
|
+
font-weight: 600;
|
|
831
|
+
letter-spacing: 0.04em;
|
|
832
|
+
text-transform: uppercase;
|
|
833
|
+
color: var(--text-muted);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/* Gray cells for the data table that follows an `<!-- example -->` marker. The
|
|
837
|
+
`.bn-example-table` class is applied by a ProseMirror decoration (see
|
|
838
|
+
exampleTableHighlight.ts), bounded to tables between the marker and the next
|
|
839
|
+
test/suite comment (or end of document). */
|
|
840
|
+
.bn-editor [data-content-type="table"].bn-example-table td,
|
|
841
|
+
.bn-editor [data-content-type="table"].bn-example-table th {
|
|
842
|
+
background: var(--bg-muted);
|
|
843
|
+
}
|
|
844
|
+
|
|
794
845
|
/* Header line: `TEST @T1233456 ............ [+]` — label, id, and add button
|
|
795
846
|
always share one row. */
|
|
796
847
|
.bn-testmeta__header {
|
|
@@ -1932,6 +1983,20 @@ html.dark .bn-step-editor--preview a.step-preview-link {
|
|
|
1932
1983
|
font-size: var(--badge-fz-sm);
|
|
1933
1984
|
}
|
|
1934
1985
|
|
|
1986
|
+
.bn-mantine .bn-suggestion-menu {
|
|
1987
|
+
max-width: 24rem;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
.bn-mt-suggestion-menu-item-body {
|
|
1991
|
+
min-width: 0;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
.bn-mt-suggestion-menu-item-subtitle {
|
|
1995
|
+
white-space: nowrap;
|
|
1996
|
+
overflow: hidden;
|
|
1997
|
+
text-overflow: ellipsis;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1935
2000
|
.bn-suggestion-icon {
|
|
1936
2001
|
display: inline-flex;
|
|
1937
2002
|
align-items: center;
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,19 @@ export {
|
|
|
23
23
|
type TagMatch,
|
|
24
24
|
} from "./editor/tagBadge";
|
|
25
25
|
|
|
26
|
+
export {
|
|
27
|
+
autoLinkExtension,
|
|
28
|
+
AutoLinkExtension,
|
|
29
|
+
detectLinks,
|
|
30
|
+
type LinkMatch,
|
|
31
|
+
} from "./editor/autoLink";
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
exampleTableHighlightExtension,
|
|
35
|
+
ExampleTableHighlightExtension,
|
|
36
|
+
markExampleTables,
|
|
37
|
+
} from "./editor/exampleTableHighlight";
|
|
38
|
+
|
|
26
39
|
export {
|
|
27
40
|
blocksToMarkdown,
|
|
28
41
|
markdownToBlocks,
|