testomatio-editor-blocks 0.4.27 → 0.4.29

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.
@@ -14,6 +14,16 @@ export declare const isEmptyParagraph: (b: any) => boolean;
14
14
  export declare function canInsertStepOrSnippet(editor: {
15
15
  document: any[];
16
16
  }, referenceBlockId: string): boolean;
17
+ /**
18
+ * Programmatically add an empty step block to the editor.
19
+ * - If a "Steps" heading exists, inserts after the last step/snippet under it.
20
+ * - Otherwise, appends a "Steps" heading + empty step at the end.
21
+ * Returns the inserted step's block ID (for focusing), or null.
22
+ */
23
+ export declare function addStepsBlock(editor: {
24
+ document: any[];
25
+ insertBlocks: (blocks: any[], referenceId: string, placement: string) => any[];
26
+ }): string | null;
17
27
  export declare const stepBlock: {
18
28
  config: {
19
29
  readonly type: "testStep";
@@ -85,6 +85,62 @@ export function canInsertStepOrSnippet(editor, referenceBlockId) {
85
85
  }
86
86
  return false;
87
87
  }
88
+ /**
89
+ * Programmatically add an empty step block to the editor.
90
+ * - If a "Steps" heading exists, inserts after the last step/snippet under it.
91
+ * - Otherwise, appends a "Steps" heading + empty step at the end.
92
+ * Returns the inserted step's block ID (for focusing), or null.
93
+ */
94
+ export function addStepsBlock(editor) {
95
+ var _a, _b, _c, _d;
96
+ const allBlocks = editor.document;
97
+ const emptyStep = {
98
+ type: "testStep",
99
+ props: { stepTitle: "", stepData: "", expectedResult: "" },
100
+ children: [],
101
+ };
102
+ let stepsHeadingIndex = -1;
103
+ for (let i = 0; i < allBlocks.length; i++) {
104
+ const b = allBlocks[i];
105
+ if (b.type !== "heading")
106
+ continue;
107
+ const text = (Array.isArray(b.content) ? b.content : [])
108
+ .filter((n) => n.type === "text")
109
+ .map((n) => { var _a; return (_a = n.text) !== null && _a !== void 0 ? _a : ""; })
110
+ .join("")
111
+ .trim()
112
+ .toLowerCase()
113
+ .replace(/[:\-–—]$/, "");
114
+ if (isStepsHeading(text)) {
115
+ stepsHeadingIndex = i;
116
+ break;
117
+ }
118
+ }
119
+ if (stepsHeadingIndex >= 0) {
120
+ let lastIndex = stepsHeadingIndex;
121
+ for (let i = stepsHeadingIndex + 1; i < allBlocks.length; i++) {
122
+ const b = allBlocks[i];
123
+ if (b.type === "testStep" || b.type === "snippet") {
124
+ lastIndex = i;
125
+ continue;
126
+ }
127
+ if (isEmptyParagraph(b))
128
+ continue;
129
+ break;
130
+ }
131
+ const inserted = editor.insertBlocks([emptyStep], allBlocks[lastIndex].id, "after");
132
+ return (_b = (_a = inserted === null || inserted === void 0 ? void 0 : inserted[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
133
+ }
134
+ const lastBlock = allBlocks[allBlocks.length - 1];
135
+ const stepsHeading = {
136
+ type: "heading",
137
+ props: { level: 3 },
138
+ content: [{ type: "text", text: "Steps" }],
139
+ children: [],
140
+ };
141
+ const inserted = editor.insertBlocks([stepsHeading, emptyStep], lastBlock.id, "after");
142
+ return (_d = (_c = inserted === null || inserted === void 0 ? void 0 : inserted[1]) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null;
143
+ }
88
144
  export const stepBlock = createReactBlockSpec({
89
145
  type: "testStep",
90
146
  content: "none",
@@ -246,6 +302,12 @@ export const stepBlock = createReactBlockSpec({
246
302
  });
247
303
  }, [editor, block.id, expectedResult]);
248
304
  const handleInsertNextStep = useCallback(() => {
305
+ const allBlocks = editor.document;
306
+ const idx = allBlocks.findIndex((b) => b.id === block.id);
307
+ const next = idx >= 0 ? allBlocks[idx + 1] : null;
308
+ if (next && isEmptyParagraph(next)) {
309
+ editor.removeBlocks([next.id]);
310
+ }
249
311
  editor.insertBlocks([
250
312
  {
251
313
  type: "testStep",
@@ -1,7 +1,20 @@
1
1
  export function createMarkdownPasteHandler(converter) {
2
2
  return ({ event, editor, defaultPasteHandler }) => {
3
- var _a, _b, _c, _d;
4
- const plainText = (_b = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData("text/plain")) !== null && _b !== void 0 ? _b : "";
3
+ var _a, _b, _c, _d, _e, _f, _g, _h;
4
+ const types = (_b = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.types) !== null && _b !== void 0 ? _b : [];
5
+ if (types.includes("blocknote/html"))
6
+ return defaultPasteHandler();
7
+ if (types.includes("vscode-editor-data"))
8
+ return defaultPasteHandler();
9
+ if (types.includes("text/html")) {
10
+ const html = (_d = (_c = event.clipboardData) === null || _c === void 0 ? void 0 : _c.getData("text/html")) !== null && _d !== void 0 ? _d : "";
11
+ if (/<(pre|code)[\s>]/i.test(html))
12
+ return defaultPasteHandler();
13
+ }
14
+ const cursorBlock = editor.getTextCursorPosition().block;
15
+ if ((cursorBlock === null || cursorBlock === void 0 ? void 0 : cursorBlock.type) === "codeBlock" || (cursorBlock === null || cursorBlock === void 0 ? void 0 : cursorBlock.type) === "quote" || (cursorBlock === null || cursorBlock === void 0 ? void 0 : cursorBlock.type) === "table")
16
+ return defaultPasteHandler();
17
+ const plainText = (_f = (_e = event.clipboardData) === null || _e === void 0 ? void 0 : _e.getData("text/plain")) !== null && _f !== void 0 ? _f : "";
5
18
  if (!plainText.trim())
6
19
  return defaultPasteHandler();
7
20
  try {
@@ -9,7 +22,7 @@ export function createMarkdownPasteHandler(converter) {
9
22
  if (parsedBlocks.length === 0)
10
23
  return defaultPasteHandler();
11
24
  const selection = editor.getSelection();
12
- const selectedIds = (_d = (_c = selection === null || selection === void 0 ? void 0 : selection.blocks) === null || _c === void 0 ? void 0 : _c.map((block) => block.id).filter((id) => Boolean(id))) !== null && _d !== void 0 ? _d : [];
25
+ const selectedIds = (_h = (_g = selection === null || selection === void 0 ? void 0 : selection.blocks) === null || _g === void 0 ? void 0 : _g.map((block) => block.id).filter((id) => Boolean(id))) !== null && _h !== void 0 ? _h : [];
13
26
  if (selectedIds.length > 0) {
14
27
  editor.replaceBlocks(selectedIds, parsedBlocks);
15
28
  }
@@ -1,5 +1,5 @@
1
1
  export { customSchema, type CustomSchema, type CustomBlock, type CustomEditor, } from "./editor/customSchema";
2
- export { stepBlock, canInsertStepOrSnippet, isStepsHeading } from "./editor/blocks/step";
2
+ export { stepBlock, canInsertStepOrSnippet, isStepsHeading, addStepsBlock } from "./editor/blocks/step";
3
3
  export { snippetBlock } from "./editor/blocks/snippet";
4
4
  export { markdownToHtml, htmlToMarkdown } from "./editor/blocks/markdown";
5
5
  export { blocksToMarkdown, markdownToBlocks, type CustomEditorBlock, type CustomPartialBlock, } from "./editor/customMarkdownConverter";
package/package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { customSchema, } from "./editor/customSchema";
2
- export { stepBlock, canInsertStepOrSnippet, isStepsHeading } from "./editor/blocks/step";
2
+ export { stepBlock, canInsertStepOrSnippet, isStepsHeading, addStepsBlock } from "./editor/blocks/step";
3
3
  export { snippetBlock } from "./editor/blocks/snippet";
4
4
  export { markdownToHtml, htmlToMarkdown } from "./editor/blocks/markdown";
5
5
  export { blocksToMarkdown, markdownToBlocks, } from "./editor/customMarkdownConverter";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testomatio-editor-blocks",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
4
4
  "description": "Custom BlockNote schema, markdown conversion helpers, and UI for Testomatio-style test cases and steps.",
5
5
  "type": "module",
6
6
  "main": "./package/index.js",
@@ -96,6 +96,66 @@ export function canInsertStepOrSnippet(
96
96
  return false;
97
97
  }
98
98
 
99
+ /**
100
+ * Programmatically add an empty step block to the editor.
101
+ * - If a "Steps" heading exists, inserts after the last step/snippet under it.
102
+ * - Otherwise, appends a "Steps" heading + empty step at the end.
103
+ * Returns the inserted step's block ID (for focusing), or null.
104
+ */
105
+ export function addStepsBlock(editor: {
106
+ document: any[];
107
+ insertBlocks: (blocks: any[], referenceId: string, placement: string) => any[];
108
+ }): string | null {
109
+ const allBlocks = editor.document;
110
+ const emptyStep = {
111
+ type: "testStep" as const,
112
+ props: { stepTitle: "", stepData: "", expectedResult: "" },
113
+ children: [],
114
+ };
115
+
116
+ let stepsHeadingIndex = -1;
117
+ for (let i = 0; i < allBlocks.length; i++) {
118
+ const b = allBlocks[i];
119
+ if (b.type !== "heading") continue;
120
+ const text = (Array.isArray(b.content) ? b.content : [])
121
+ .filter((n: any) => n.type === "text")
122
+ .map((n: any) => n.text ?? "")
123
+ .join("")
124
+ .trim()
125
+ .toLowerCase()
126
+ .replace(/[:\-–—]$/, "");
127
+ if (isStepsHeading(text)) {
128
+ stepsHeadingIndex = i;
129
+ break;
130
+ }
131
+ }
132
+
133
+ if (stepsHeadingIndex >= 0) {
134
+ let lastIndex = stepsHeadingIndex;
135
+ for (let i = stepsHeadingIndex + 1; i < allBlocks.length; i++) {
136
+ const b = allBlocks[i];
137
+ if (b.type === "testStep" || b.type === "snippet") {
138
+ lastIndex = i;
139
+ continue;
140
+ }
141
+ if (isEmptyParagraph(b)) continue;
142
+ break;
143
+ }
144
+ const inserted = editor.insertBlocks([emptyStep], allBlocks[lastIndex].id, "after");
145
+ return inserted?.[0]?.id ?? null;
146
+ }
147
+
148
+ const lastBlock = allBlocks[allBlocks.length - 1];
149
+ const stepsHeading = {
150
+ type: "heading" as const,
151
+ props: { level: 3 },
152
+ content: [{ type: "text" as const, text: "Steps" }],
153
+ children: [],
154
+ };
155
+ const inserted = editor.insertBlocks([stepsHeading, emptyStep], lastBlock.id, "after");
156
+ return inserted?.[1]?.id ?? null;
157
+ }
158
+
99
159
  export const stepBlock = createReactBlockSpec(
100
160
  {
101
161
  type: "testStep",
@@ -287,6 +347,12 @@ export const stepBlock = createReactBlockSpec(
287
347
  );
288
348
 
289
349
  const handleInsertNextStep = useCallback(() => {
350
+ const allBlocks = editor.document;
351
+ const idx = allBlocks.findIndex((b: any) => b.id === block.id);
352
+ const next = idx >= 0 ? allBlocks[idx + 1] : null;
353
+ if (next && isEmptyParagraph(next)) {
354
+ editor.removeBlocks([next.id]);
355
+ }
290
356
  editor.insertBlocks(
291
357
  [
292
358
  {
@@ -14,6 +14,19 @@ export function createMarkdownPasteHandler(
14
14
  converter: (markdown: string) => CustomPartialBlock[],
15
15
  ) {
16
16
  return ({ event, editor, defaultPasteHandler }: PasteHandlerContext): boolean | undefined => {
17
+ const types = event.clipboardData?.types ?? [];
18
+
19
+ if (types.includes("blocknote/html")) return defaultPasteHandler();
20
+ if (types.includes("vscode-editor-data")) return defaultPasteHandler();
21
+
22
+ if (types.includes("text/html")) {
23
+ const html = event.clipboardData?.getData("text/html") ?? "";
24
+ if (/<(pre|code)[\s>]/i.test(html)) return defaultPasteHandler();
25
+ }
26
+
27
+ const cursorBlock = editor.getTextCursorPosition().block;
28
+ if (cursorBlock?.type === "codeBlock" || cursorBlock?.type === "quote" || cursorBlock?.type === "table") return defaultPasteHandler();
29
+
17
30
  const plainText = event.clipboardData?.getData("text/plain") ?? "";
18
31
  if (!plainText.trim()) return defaultPasteHandler();
19
32
 
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ export {
4
4
  type CustomBlock,
5
5
  type CustomEditor,
6
6
  } from "./editor/customSchema";
7
- export { stepBlock, canInsertStepOrSnippet, isStepsHeading } from "./editor/blocks/step";
7
+ export { stepBlock, canInsertStepOrSnippet, isStepsHeading, addStepsBlock } from "./editor/blocks/step";
8
8
  export { snippetBlock } from "./editor/blocks/snippet";
9
9
  export { markdownToHtml, htmlToMarkdown } from "./editor/blocks/markdown";
10
10