testomatio-editor-blocks 0.1.1 → 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/README.md +2 -7
- package/package/editor/stepAutocomplete.js +14 -1
- package/package/editor/stepImageUpload.d.ts +1 -1
- package/package/editor/stepImageUpload.js +4 -9
- package/package/index.d.ts +1 -1
- package/package/index.js +1 -1
- package/package.json +1 -1
- package/src/App.tsx +18 -4
- package/src/editor/stepAutocomplete.test.ts +20 -0
- package/src/editor/stepAutocomplete.tsx +14 -1
- package/src/editor/stepImageUpload.test.ts +25 -0
- package/src/editor/stepImageUpload.ts +11 -0
- package/src/index.ts +1 -1
- package/src/editor/stepImageUpload.tsx +0 -19
package/README.md
CHANGED
|
@@ -127,13 +127,8 @@ setGlobalStepSuggestionsFetcher(async () => {
|
|
|
127
127
|
return res.json();
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
// Image upload
|
|
131
|
-
|
|
132
|
-
const formData = new FormData();
|
|
133
|
-
formData.append("file", image);
|
|
134
|
-
const res = await fetch("https://api.testomatio.com/v1/uploads", { method: "POST", body: formData });
|
|
135
|
-
return res.json(); // must resolve to { url: "https://..." }
|
|
136
|
-
});
|
|
130
|
+
// Image upload uses BlockNote's `uploadFile` handler you pass to `useCreateBlockNote`.
|
|
131
|
+
// No extra setup is required for step fields.
|
|
137
132
|
```
|
|
138
133
|
|
|
139
134
|
Step suggestions accept either an array of `{ id, title, ... }` or the JSON:API shape:
|
|
@@ -6,7 +6,20 @@ export function setGlobalStepSuggestionsFetcher(fetcher) {
|
|
|
6
6
|
cachedSuggestions = [];
|
|
7
7
|
}
|
|
8
8
|
export function useStepAutocomplete() {
|
|
9
|
-
const [suggestions, setSuggestions] = useState(
|
|
9
|
+
const [suggestions, setSuggestions] = useState(() => {
|
|
10
|
+
if (cachedSuggestions.length > 0) {
|
|
11
|
+
return cachedSuggestions;
|
|
12
|
+
}
|
|
13
|
+
if (globalFetcher) {
|
|
14
|
+
const result = globalFetcher();
|
|
15
|
+
if (!result || typeof result.then !== "function") {
|
|
16
|
+
const normalized = normalizeStepSuggestions(result);
|
|
17
|
+
cachedSuggestions = normalized;
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
});
|
|
10
23
|
useEffect(() => {
|
|
11
24
|
if (suggestions.length > 0) {
|
|
12
25
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type StepImageUploadHandler = (image: Blob) => Promise<{
|
|
2
2
|
url: string;
|
|
3
3
|
}>;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function setImageUploadHandler(handler: StepImageUploadHandler | null): void;
|
|
5
5
|
export declare function useStepImageUpload(): StepImageUploadHandler | null;
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
globalUploadHandler = handler;
|
|
1
|
+
let imageUploadHandler = null;
|
|
2
|
+
export function setImageUploadHandler(handler) {
|
|
3
|
+
imageUploadHandler = handler;
|
|
5
4
|
}
|
|
6
5
|
export function useStepImageUpload() {
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
setHandler(globalUploadHandler);
|
|
10
|
-
}, []);
|
|
11
|
-
return handler;
|
|
6
|
+
return imageUploadHandler;
|
|
12
7
|
}
|
package/package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { customSchema, type CustomSchema, type CustomBlock, type CustomEditor, } from "./editor/customSchema";
|
|
2
2
|
export { blocksToMarkdown, markdownToBlocks, type CustomEditorBlock, type CustomPartialBlock, } from "./editor/customMarkdownConverter";
|
|
3
3
|
export { useStepAutocomplete, parseStepsFromJsonApi, setGlobalStepSuggestionsFetcher, type StepSuggestion, type StepJsonApiDocument, type StepJsonApiResource, } from "./editor/stepAutocomplete";
|
|
4
|
-
export { useStepImageUpload,
|
|
4
|
+
export { useStepImageUpload, setImageUploadHandler, type StepImageUploadHandler, } from "./editor/stepImageUpload";
|
|
5
5
|
export declare const testomatioEditorClassName = "markdown testomatio-editor";
|
package/package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { customSchema, } from "./editor/customSchema";
|
|
2
2
|
export { blocksToMarkdown, markdownToBlocks, } from "./editor/customMarkdownConverter";
|
|
3
3
|
export { useStepAutocomplete, parseStepsFromJsonApi, setGlobalStepSuggestionsFetcher, } from "./editor/stepAutocomplete";
|
|
4
|
-
export { useStepImageUpload,
|
|
4
|
+
export { useStepImageUpload, setImageUploadHandler, } from "./editor/stepImageUpload";
|
|
5
5
|
export const testomatioEditorClassName = "markdown testomatio-editor";
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "./editor/customMarkdownConverter";
|
|
20
20
|
import { customSchema, type CustomEditor } from "./editor/customSchema";
|
|
21
21
|
import { setGlobalStepSuggestionsFetcher, type StepJsonApiDocument } from "./editor/stepAutocomplete";
|
|
22
|
-
import {
|
|
22
|
+
import { setImageUploadHandler } from "./editor/stepImageUpload";
|
|
23
23
|
import "./App.css";
|
|
24
24
|
|
|
25
25
|
const focusTestStepTitle = (editor: CustomEditor | null | undefined, blockId?: string) => {
|
|
@@ -348,13 +348,27 @@ function App() {
|
|
|
348
348
|
useEffect(() => {
|
|
349
349
|
// Demo defaults: configure global handlers so the editor works without manual providers.
|
|
350
350
|
setGlobalStepSuggestionsFetcher(() => DEMO_STEP_FIXTURES);
|
|
351
|
-
|
|
351
|
+
|
|
352
|
+
const handler = editor?.uploadFile
|
|
353
|
+
? async (file: Blob) => {
|
|
354
|
+
const result = await editor.uploadFile!(file as File);
|
|
355
|
+
if (typeof result === "string") {
|
|
356
|
+
return { url: result };
|
|
357
|
+
}
|
|
358
|
+
if (result && typeof result === "object" && "url" in result && typeof (result as any).url === "string") {
|
|
359
|
+
return { url: (result as any).url as string };
|
|
360
|
+
}
|
|
361
|
+
throw new Error("uploadFile did not return a URL");
|
|
362
|
+
}
|
|
363
|
+
: uploadStepImage;
|
|
364
|
+
|
|
365
|
+
setImageUploadHandler(handler);
|
|
352
366
|
|
|
353
367
|
return () => {
|
|
354
368
|
setGlobalStepSuggestionsFetcher(null);
|
|
355
|
-
|
|
369
|
+
setImageUploadHandler(null);
|
|
356
370
|
};
|
|
357
|
-
}, [uploadStepImage]);
|
|
371
|
+
}, [editor, uploadStepImage]);
|
|
358
372
|
|
|
359
373
|
const createTestCaseBlock = useMemo<() => CustomPartialBlock>(() => {
|
|
360
374
|
return () => ({
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { parseStepsFromJsonApi } from "./stepAutocomplete";
|
|
3
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { setGlobalStepSuggestionsFetcher, useStepAutocomplete } from "./stepAutocomplete";
|
|
3
6
|
|
|
4
7
|
describe("parseStepsFromJsonApi", () => {
|
|
5
8
|
it("converts JSON:API resources into step suggestions", () => {
|
|
@@ -80,4 +83,21 @@ describe("parseStepsFromJsonApi", () => {
|
|
|
80
83
|
"Click submit",
|
|
81
84
|
]);
|
|
82
85
|
});
|
|
86
|
+
|
|
87
|
+
it("reads suggestions from the global fetcher via useStepAutocomplete", () => {
|
|
88
|
+
setGlobalStepSuggestionsFetcher(() => [{ id: "1", title: "Global step" }]);
|
|
89
|
+
|
|
90
|
+
let seen: any[] = [];
|
|
91
|
+
const Probe = () => {
|
|
92
|
+
seen = useStepAutocomplete();
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
renderToStaticMarkup(React.createElement(Probe));
|
|
97
|
+
|
|
98
|
+
expect(seen).toEqual([{ id: "1", title: "Global step" }]);
|
|
99
|
+
|
|
100
|
+
// reset for other tests
|
|
101
|
+
setGlobalStepSuggestionsFetcher(null);
|
|
102
|
+
});
|
|
83
103
|
});
|
|
@@ -46,7 +46,20 @@ export function setGlobalStepSuggestionsFetcher(fetcher: StepSuggestionsFetcher
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export function useStepAutocomplete(): StepSuggestion[] {
|
|
49
|
-
const [suggestions, setSuggestions] = useState<StepSuggestion[]>(
|
|
49
|
+
const [suggestions, setSuggestions] = useState<StepSuggestion[]>(() => {
|
|
50
|
+
if (cachedSuggestions.length > 0) {
|
|
51
|
+
return cachedSuggestions;
|
|
52
|
+
}
|
|
53
|
+
if (globalFetcher) {
|
|
54
|
+
const result = globalFetcher();
|
|
55
|
+
if (!result || typeof (result as Promise<unknown>).then !== "function") {
|
|
56
|
+
const normalized = normalizeStepSuggestions(result as StepInput);
|
|
57
|
+
cachedSuggestions = normalized;
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
});
|
|
50
63
|
|
|
51
64
|
useEffect(() => {
|
|
52
65
|
if (suggestions.length > 0) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { setImageUploadHandler, useStepImageUpload, type StepImageUploadHandler } from "./stepImageUpload";
|
|
5
|
+
|
|
6
|
+
describe("image upload handler hook", () => {
|
|
7
|
+
it("returns the configured upload handler", async () => {
|
|
8
|
+
const handler: StepImageUploadHandler = async () => ({ url: "https://example.com/image.png" });
|
|
9
|
+
setImageUploadHandler(handler);
|
|
10
|
+
|
|
11
|
+
let seen: any;
|
|
12
|
+
const Probe = () => {
|
|
13
|
+
seen = useStepImageUpload();
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
renderToStaticMarkup(React.createElement(Probe));
|
|
18
|
+
|
|
19
|
+
expect(seen).toBe(handler);
|
|
20
|
+
const result = await (seen as StepImageUploadHandler)(new Blob(["demo"], { type: "image/png" }));
|
|
21
|
+
expect(result).toEqual({ url: "https://example.com/image.png" });
|
|
22
|
+
|
|
23
|
+
setImageUploadHandler(null);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type StepImageUploadHandler = (image: Blob) => Promise<{ url: string }>;
|
|
2
|
+
|
|
3
|
+
let imageUploadHandler: StepImageUploadHandler | null = null;
|
|
4
|
+
|
|
5
|
+
export function setImageUploadHandler(handler: StepImageUploadHandler | null) {
|
|
6
|
+
imageUploadHandler = handler;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useStepImageUpload(): StepImageUploadHandler | null {
|
|
10
|
+
return imageUploadHandler;
|
|
11
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export type StepImageUploadHandler = (image: Blob) => Promise<{ url: string }>;
|
|
4
|
-
|
|
5
|
-
let globalUploadHandler: StepImageUploadHandler | null = null;
|
|
6
|
-
|
|
7
|
-
export function setGlobalStepImageUploadHandler(handler: StepImageUploadHandler | null) {
|
|
8
|
-
globalUploadHandler = handler;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function useStepImageUpload(): StepImageUploadHandler | null {
|
|
12
|
-
const [handler, setHandler] = useState<StepImageUploadHandler | null>(globalUploadHandler);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
setHandler(globalUploadHandler);
|
|
16
|
-
}, []);
|
|
17
|
-
|
|
18
|
-
return handler;
|
|
19
|
-
}
|