testomatio-editor-blocks 0.4.26 → 0.4.27
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/createMarkdownPasteHandler.d.ts +12 -0
- package/package/editor/createMarkdownPasteHandler.js +36 -0
- package/package/editor/customMarkdownConverter.js +1 -2
- package/package/index.d.ts +1 -0
- package/package/index.js +1 -0
- package/package/styles.css +2 -2
- package/package.json +1 -1
- package/src/App.tsx +2 -40
- package/src/editor/createMarkdownPasteHandler.ts +49 -0
- package/src/editor/customMarkdownConverter.ts +1 -2
- package/src/editor/styles.css +2 -2
- package/src/index.ts +2 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BlockNoteEditor } from "@blocknote/core";
|
|
2
|
+
import type { CustomPartialBlock } from "./customMarkdownConverter";
|
|
3
|
+
type PasteHandlerContext = {
|
|
4
|
+
event: ClipboardEvent;
|
|
5
|
+
editor: BlockNoteEditor<any, any, any>;
|
|
6
|
+
defaultPasteHandler: (context?: {
|
|
7
|
+
prioritizeMarkdownOverHTML?: boolean;
|
|
8
|
+
plainTextAsMarkdown?: boolean;
|
|
9
|
+
}) => boolean | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare function createMarkdownPasteHandler(converter: (markdown: string) => CustomPartialBlock[]): ({ event, editor, defaultPasteHandler }: PasteHandlerContext) => boolean | undefined;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function createMarkdownPasteHandler(converter) {
|
|
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 : "";
|
|
5
|
+
if (!plainText.trim())
|
|
6
|
+
return defaultPasteHandler();
|
|
7
|
+
try {
|
|
8
|
+
const parsedBlocks = converter(plainText);
|
|
9
|
+
if (parsedBlocks.length === 0)
|
|
10
|
+
return defaultPasteHandler();
|
|
11
|
+
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 : [];
|
|
13
|
+
if (selectedIds.length > 0) {
|
|
14
|
+
editor.replaceBlocks(selectedIds, parsedBlocks);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
const cursorBlock = editor.getTextCursorPosition().block;
|
|
18
|
+
if (cursorBlock) {
|
|
19
|
+
editor.replaceBlocks([cursorBlock.id], parsedBlocks);
|
|
20
|
+
}
|
|
21
|
+
else if (editor.document.length > 0) {
|
|
22
|
+
const reference = editor.document[editor.document.length - 1];
|
|
23
|
+
editor.insertBlocks(parsedBlocks, reference.id, "after");
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
return defaultPasteHandler();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
editor.focus();
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return defaultPasteHandler();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -464,9 +464,8 @@ function serializeBlocks(blocks, ctx) {
|
|
|
464
464
|
export function blocksToMarkdown(blocks) {
|
|
465
465
|
const lines = serializeBlocks(blocks, { listDepth: 0, insideQuote: false });
|
|
466
466
|
const cleaned = lines
|
|
467
|
-
// Collapse excessive blank lines but preserve one extra for empty paragraphs.
|
|
468
467
|
.join("\n")
|
|
469
|
-
.replace(/\n{
|
|
468
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
470
469
|
.trimEnd();
|
|
471
470
|
return cleaned;
|
|
472
471
|
}
|
package/package/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export { markdownToHtml, htmlToMarkdown } from "./editor/blocks/markdown";
|
|
|
5
5
|
export { blocksToMarkdown, markdownToBlocks, type CustomEditorBlock, type CustomPartialBlock, } from "./editor/customMarkdownConverter";
|
|
6
6
|
export { useStepAutocomplete, parseStepsFromJsonApi, setStepsFetcher, type StepSuggestion, type StepJsonApiDocument, type StepJsonApiResource, } from "./editor/stepAutocomplete";
|
|
7
7
|
export { useStepImageUpload, setImageUploadHandler, type StepImageUploadHandler, } from "./editor/stepImageUpload";
|
|
8
|
+
export { createMarkdownPasteHandler } from "./editor/createMarkdownPasteHandler";
|
|
8
9
|
export declare const testomatioEditorClassName = "markdown testomatio-editor";
|
package/package/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { markdownToHtml, htmlToMarkdown } from "./editor/blocks/markdown";
|
|
|
5
5
|
export { blocksToMarkdown, markdownToBlocks, } from "./editor/customMarkdownConverter";
|
|
6
6
|
export { useStepAutocomplete, parseStepsFromJsonApi, setStepsFetcher, } from "./editor/stepAutocomplete";
|
|
7
7
|
export { useStepImageUpload, setImageUploadHandler, } from "./editor/stepImageUpload";
|
|
8
|
+
export { createMarkdownPasteHandler } from "./editor/createMarkdownPasteHandler";
|
|
8
9
|
export const testomatioEditorClassName = "markdown testomatio-editor";
|
package/package/styles.css
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
--color-accent-500: #10b981;
|
|
26
26
|
|
|
27
27
|
/* Selection */
|
|
28
|
-
--color-selection:
|
|
28
|
+
--color-selection: rgba(0, 120, 215, 0.3);
|
|
29
29
|
|
|
30
30
|
/* Semantic tokens - these reference the palette above */
|
|
31
31
|
--text-primary: #262626;
|
|
@@ -1162,7 +1162,7 @@ html.dark .bn-step-image-preview__content {
|
|
|
1162
1162
|
}
|
|
1163
1163
|
|
|
1164
1164
|
.bn-step-field:focus-within .overtype-wrapper .overtype-input::selection {
|
|
1165
|
-
background-color:
|
|
1165
|
+
background-color: var(--color-selection) !important;
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
1168
|
/* Hide OverType's built-in link tooltip — we use our own */
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type CustomEditorBlock,
|
|
18
18
|
type CustomPartialBlock,
|
|
19
19
|
} from "./editor/customMarkdownConverter";
|
|
20
|
+
import { createMarkdownPasteHandler } from "./editor/createMarkdownPasteHandler";
|
|
20
21
|
import { customSchema, type CustomEditor } from "./editor/customSchema";
|
|
21
22
|
import { setStepsFetcher, type StepJsonApiDocument } from "./editor/stepAutocomplete";
|
|
22
23
|
import { setSnippetFetcher, type SnippetJsonApiDocument } from "./editor/snippetAutocomplete";
|
|
@@ -347,46 +348,7 @@ function CustomSlashMenu() {
|
|
|
347
348
|
function App() {
|
|
348
349
|
const editor = useCreateBlockNote({
|
|
349
350
|
schema: customSchema,
|
|
350
|
-
pasteHandler: (
|
|
351
|
-
const plainText = event.clipboardData?.getData("text/plain") ?? "";
|
|
352
|
-
|
|
353
|
-
if (!plainText.trim()) {
|
|
354
|
-
return defaultPasteHandler();
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
const parsedBlocks = markdownToBlocks(plainText);
|
|
359
|
-
|
|
360
|
-
if (parsedBlocks.length === 0) {
|
|
361
|
-
return defaultPasteHandler();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const selection = editor.getSelection();
|
|
365
|
-
const selectedIds = selection?.blocks
|
|
366
|
-
?.map((block) => block.id)
|
|
367
|
-
.filter((id): id is string => Boolean(id)) ?? [];
|
|
368
|
-
|
|
369
|
-
if (selectedIds.length > 0) {
|
|
370
|
-
editor.replaceBlocks(selectedIds, parsedBlocks);
|
|
371
|
-
} else {
|
|
372
|
-
const cursorBlock = editor.getTextCursorPosition().block;
|
|
373
|
-
if (cursorBlock) {
|
|
374
|
-
editor.replaceBlocks([cursorBlock.id], parsedBlocks);
|
|
375
|
-
} else if (editor.document.length > 0) {
|
|
376
|
-
const reference = editor.document[editor.document.length - 1];
|
|
377
|
-
editor.insertBlocks(parsedBlocks, reference.id, "after");
|
|
378
|
-
} else {
|
|
379
|
-
return defaultPasteHandler();
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
editor.focus();
|
|
384
|
-
return true;
|
|
385
|
-
} catch (error) {
|
|
386
|
-
console.error("Failed to paste custom markdown", error);
|
|
387
|
-
return defaultPasteHandler();
|
|
388
|
-
}
|
|
389
|
-
},
|
|
351
|
+
pasteHandler: createMarkdownPasteHandler(markdownToBlocks),
|
|
390
352
|
});
|
|
391
353
|
const [markdown, setMarkdown] = useState("");
|
|
392
354
|
const [conversionError, setConversionError] = useState<string | null>(null);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BlockNoteEditor } from "@blocknote/core";
|
|
2
|
+
import type { CustomPartialBlock } from "./customMarkdownConverter";
|
|
3
|
+
|
|
4
|
+
type PasteHandlerContext = {
|
|
5
|
+
event: ClipboardEvent;
|
|
6
|
+
editor: BlockNoteEditor<any, any, any>;
|
|
7
|
+
defaultPasteHandler: (context?: {
|
|
8
|
+
prioritizeMarkdownOverHTML?: boolean;
|
|
9
|
+
plainTextAsMarkdown?: boolean;
|
|
10
|
+
}) => boolean | undefined;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function createMarkdownPasteHandler(
|
|
14
|
+
converter: (markdown: string) => CustomPartialBlock[],
|
|
15
|
+
) {
|
|
16
|
+
return ({ event, editor, defaultPasteHandler }: PasteHandlerContext): boolean | undefined => {
|
|
17
|
+
const plainText = event.clipboardData?.getData("text/plain") ?? "";
|
|
18
|
+
if (!plainText.trim()) return defaultPasteHandler();
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const parsedBlocks = converter(plainText);
|
|
22
|
+
if (parsedBlocks.length === 0) return defaultPasteHandler();
|
|
23
|
+
|
|
24
|
+
const selection = editor.getSelection();
|
|
25
|
+
const selectedIds = selection?.blocks
|
|
26
|
+
?.map((block: any) => block.id)
|
|
27
|
+
.filter((id: unknown): id is string => Boolean(id)) ?? [];
|
|
28
|
+
|
|
29
|
+
if (selectedIds.length > 0) {
|
|
30
|
+
editor.replaceBlocks(selectedIds, parsedBlocks);
|
|
31
|
+
} else {
|
|
32
|
+
const cursorBlock = editor.getTextCursorPosition().block;
|
|
33
|
+
if (cursorBlock) {
|
|
34
|
+
editor.replaceBlocks([cursorBlock.id], parsedBlocks);
|
|
35
|
+
} else if (editor.document.length > 0) {
|
|
36
|
+
const reference = editor.document[editor.document.length - 1];
|
|
37
|
+
editor.insertBlocks(parsedBlocks, reference.id, "after");
|
|
38
|
+
} else {
|
|
39
|
+
return defaultPasteHandler();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
editor.focus();
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return defaultPasteHandler();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -578,9 +578,8 @@ function serializeBlocks(blocks: CustomEditorBlock[], ctx: MarkdownContext): str
|
|
|
578
578
|
export function blocksToMarkdown(blocks: CustomEditorBlock[]): string {
|
|
579
579
|
const lines = serializeBlocks(blocks, { listDepth: 0, insideQuote: false });
|
|
580
580
|
const cleaned = lines
|
|
581
|
-
// Collapse excessive blank lines but preserve one extra for empty paragraphs.
|
|
582
581
|
.join("\n")
|
|
583
|
-
.replace(/\n{
|
|
582
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
584
583
|
.trimEnd();
|
|
585
584
|
|
|
586
585
|
return cleaned;
|
package/src/editor/styles.css
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
--color-accent-500: #10b981;
|
|
26
26
|
|
|
27
27
|
/* Selection */
|
|
28
|
-
--color-selection:
|
|
28
|
+
--color-selection: rgba(0, 120, 215, 0.3);
|
|
29
29
|
|
|
30
30
|
/* Semantic tokens - these reference the palette above */
|
|
31
31
|
--text-primary: #262626;
|
|
@@ -1162,7 +1162,7 @@ html.dark .bn-step-image-preview__content {
|
|
|
1162
1162
|
}
|
|
1163
1163
|
|
|
1164
1164
|
.bn-step-field:focus-within .overtype-wrapper .overtype-input::selection {
|
|
1165
|
-
background-color:
|
|
1165
|
+
background-color: var(--color-selection) !important;
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
1168
|
/* Hide OverType's built-in link tooltip — we use our own */
|
package/src/index.ts
CHANGED