@supernova-studio/client 0.0.3 → 0.0.4
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.json +2 -2
- package/src/api/index.ts +1 -0
- package/src/api/responses/get-block-definitions.ts +8 -0
- package/src/api/responses/index.ts +1 -0
- package/src/docs-editor/blocks-to-prosemirror.ts +372 -0
- package/src/docs-editor/index.ts +5 -0
- package/src/docs-editor/model/block.ts +9 -0
- package/src/docs-editor/model/index.ts +2 -0
- package/src/docs-editor/model/page.ts +8 -0
- package/src/docs-editor/prosemirror/index.ts +2 -0
- package/src/docs-editor/prosemirror/schema.ts +1253 -0
- package/src/docs-editor/prosemirror/types.ts +23 -0
- package/src/docs-editor/prosemirror-to-blocks.ts +170 -0
- package/src/docs-editor/utils.ts +81 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ProsemirrorNode = {
|
|
2
|
+
type: string;
|
|
3
|
+
text?: string;
|
|
4
|
+
attrs?: Record<string, any>;
|
|
5
|
+
marks?: ProsemirrorMark[];
|
|
6
|
+
content?: ProsemirrorNode[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type ProsemirrorMark = {
|
|
10
|
+
type: string;
|
|
11
|
+
attrs: Record<string, any>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ProsemirrorBlockItem = {
|
|
15
|
+
properties: ProsemirrorBlockItemPropertyValue[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ProsemirrorBlockItemPropertyValue = {
|
|
19
|
+
id: string;
|
|
20
|
+
data: {
|
|
21
|
+
value?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as Y from "yjs";
|
|
2
|
+
import { ProsemirrorNode, ProsemirrorMark } from "./prosemirror/types";
|
|
3
|
+
import {
|
|
4
|
+
PageBlockDefinition,
|
|
5
|
+
PageBlockText,
|
|
6
|
+
PageBlockTextSpan,
|
|
7
|
+
PageBlockTextSpanAttribute,
|
|
8
|
+
PageBlockDefinitionProperty,
|
|
9
|
+
} from "@supernova-studio/model";
|
|
10
|
+
import { PageBlockEditorModel } from "./model/block";
|
|
11
|
+
import { DocumentationPageEditorModel } from "./model/page";
|
|
12
|
+
import { BlockDefinitionUtils } from "./utils";
|
|
13
|
+
import { yXmlFragmentToProsemirrorJSON } from "y-prosemirror";
|
|
14
|
+
|
|
15
|
+
export function yXmlFragmetToPage(
|
|
16
|
+
fragment: Y.XmlFragment,
|
|
17
|
+
definitions: PageBlockDefinition[]
|
|
18
|
+
): DocumentationPageEditorModel {
|
|
19
|
+
const prosemirrorJson = yXmlFragmentToProsemirrorJSON(fragment) as ProsemirrorNode;
|
|
20
|
+
return prosemirrorDocToPage(prosemirrorJson, definitions);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function prosemirrorDocToPage(
|
|
24
|
+
prosemirrorDoc: ProsemirrorNode,
|
|
25
|
+
definitions: PageBlockDefinition[]
|
|
26
|
+
): DocumentationPageEditorModel {
|
|
27
|
+
const definitionsById = mapByUnique(definitions, d => d.id);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
blocks: (prosemirrorDoc.content ?? [])
|
|
31
|
+
.map(prosemirrorNode => {
|
|
32
|
+
const definitionId = prosemirrorNode.attrs?.definitionId;
|
|
33
|
+
if (!definitionId) throw new Error(`Node is missing defintion id`);
|
|
34
|
+
if (typeof definitionId !== "string")
|
|
35
|
+
throw new Error(`Definition id is ${typeof definitionId}, has to be string`);
|
|
36
|
+
|
|
37
|
+
const definition = definitionsById.get(definitionId);
|
|
38
|
+
if (!definition) throw new Error(`Definition by id ${definitionId} doesn't exist`);
|
|
39
|
+
|
|
40
|
+
return prosemirrorNodeToBlock(prosemirrorNode, definition);
|
|
41
|
+
})
|
|
42
|
+
.filter(nonNullFilter),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function prosemirrorNodeToBlock(
|
|
47
|
+
prosemirrorNode: ProsemirrorNode,
|
|
48
|
+
definition: PageBlockDefinition
|
|
49
|
+
): PageBlockEditorModel | null {
|
|
50
|
+
const richTextProperty = BlockDefinitionUtils.firstRichTextProperty(definition);
|
|
51
|
+
if (richTextProperty) {
|
|
52
|
+
return parseAsRichText(prosemirrorNode, definition, richTextProperty);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parseAsCustomBlock(prosemirrorNode, definition);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseAsRichText(
|
|
59
|
+
prosemirrorNode: ProsemirrorNode,
|
|
60
|
+
definition: PageBlockDefinition,
|
|
61
|
+
property: PageBlockDefinitionProperty
|
|
62
|
+
): PageBlockEditorModel {
|
|
63
|
+
const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
|
|
64
|
+
const variantId = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "variantId");
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
// TODO Artem: indent
|
|
68
|
+
id: id,
|
|
69
|
+
data: {
|
|
70
|
+
packageId: definition.id,
|
|
71
|
+
indentLevel: 0,
|
|
72
|
+
variantId: variantId,
|
|
73
|
+
items: [
|
|
74
|
+
{
|
|
75
|
+
props: {
|
|
76
|
+
[property.id]: {
|
|
77
|
+
value: parseRichText(prosemirrorNode.content ?? []),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseRichText(spans: ProsemirrorNode[]): PageBlockText {
|
|
87
|
+
return {
|
|
88
|
+
spans: spans.map(parseRichTextSpan).filter(nonNullFilter),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseRichTextSpan(span: ProsemirrorNode): PageBlockTextSpan | null {
|
|
93
|
+
if (span.type !== "text" || !span.text) return null;
|
|
94
|
+
|
|
95
|
+
const marks = span.marks ?? [];
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
text: span.text,
|
|
99
|
+
attributes: marks.map(parseRichTextAttribute).filter(nonNullFilter),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseRichTextAttribute(mark: ProsemirrorMark): PageBlockTextSpanAttribute | null {
|
|
104
|
+
switch (mark.type) {
|
|
105
|
+
case "bold":
|
|
106
|
+
return { type: "Bold" };
|
|
107
|
+
case "italic":
|
|
108
|
+
return { type: "Italic" };
|
|
109
|
+
case "strikethrough":
|
|
110
|
+
return { type: "Strikethrough" };
|
|
111
|
+
case "code":
|
|
112
|
+
return { type: "Code" };
|
|
113
|
+
case "link":
|
|
114
|
+
const itemId = mark.attrs?.itemId;
|
|
115
|
+
const href = mark.attrs?.href;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
type: "Link",
|
|
119
|
+
openInNewWindow: mark.attrs?.target !== "_self",
|
|
120
|
+
documentationItemId: itemId,
|
|
121
|
+
link: href,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//
|
|
129
|
+
// Custom block
|
|
130
|
+
//
|
|
131
|
+
|
|
132
|
+
function parseAsCustomBlock(
|
|
133
|
+
prosemirrorNode: ProsemirrorNode,
|
|
134
|
+
definition: PageBlockDefinition
|
|
135
|
+
): PageBlockEditorModel | null {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//
|
|
140
|
+
// Attributes
|
|
141
|
+
//
|
|
142
|
+
|
|
143
|
+
function parseProsemirrorBlockAttribute(prosemirrorNode: ProsemirrorNode, attributeName: string) {
|
|
144
|
+
const attributeValue = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, attributeName); //prosemirrorNode.attrs?.[attributeName];
|
|
145
|
+
if (!attributeValue) {
|
|
146
|
+
throw new Error(`Attribute ${attributeName} was not defined`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return attributeValue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function parseProsemirrorOptionalBlockAttribute(prosemirrorNode: ProsemirrorNode, attributeName: string) {
|
|
153
|
+
return prosemirrorNode.attrs?.[attributeName] ?? undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//
|
|
157
|
+
// Utils
|
|
158
|
+
//
|
|
159
|
+
|
|
160
|
+
function nonNullFilter<T>(item: T | null): item is T {
|
|
161
|
+
return item !== null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function mapByUnique<V, K>(items: V[], keyFn: (item: V) => K): Map<K, V> {
|
|
165
|
+
const result = new Map<K, V>();
|
|
166
|
+
for (const item of items) {
|
|
167
|
+
result.set(keyFn(item), item);
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { PageBlockEditorModel } from "./model/block";
|
|
2
|
+
import {
|
|
3
|
+
PageBlockDefinition,
|
|
4
|
+
PageBlockDefinitionPropertyType,
|
|
5
|
+
PageBlockItemV2,
|
|
6
|
+
PageBlockText,
|
|
7
|
+
} from "@supernova-studio/model";
|
|
8
|
+
|
|
9
|
+
export const SDKBlockParsingUtils = {
|
|
10
|
+
singleBlockItem(block: PageBlockEditorModel) {
|
|
11
|
+
if (!block.data.items.length) throw new Error(`Block ${block.id} has no items`);
|
|
12
|
+
if (block.data.items.length > 1) throw new Error(`Block ${block.id} has more than 1 item`);
|
|
13
|
+
|
|
14
|
+
return block.data.items[0];
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
richTextPropertyValue(item: PageBlockItemV2, propertyKey: string) {
|
|
18
|
+
const objectValue = this.objectPropertyValue(item, propertyKey);
|
|
19
|
+
const richText = PageBlockText.parse(objectValue);
|
|
20
|
+
|
|
21
|
+
return richText;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
objectPropertyValue(item: PageBlockItemV2, propertyKey: string) {
|
|
25
|
+
const value = this.propertyValue(item, propertyKey);
|
|
26
|
+
|
|
27
|
+
if (typeof value !== "object") {
|
|
28
|
+
throw new Error(`Value for property ${propertyKey} is expected to be an object, but instead is ${typeof value}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return value;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
propertyValue(item: PageBlockItemV2, propertyKey: string) {
|
|
35
|
+
const value = this.safePropertyValue(item, propertyKey);
|
|
36
|
+
if (!value) throw new Error(`Property ${propertyKey} is not defined on block item`);
|
|
37
|
+
|
|
38
|
+
return value;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
safePropertyValue(item: PageBlockItemV2, propertyKey: string) {
|
|
42
|
+
return item.props[propertyKey] ?? undefined;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const BlockDefinitionUtils = {
|
|
47
|
+
firstRichTextProperty(definition: PageBlockDefinition) {
|
|
48
|
+
const property = definition.item.properties.find(p => p.type === PageBlockDefinitionPropertyType.richText);
|
|
49
|
+
if (property) return property;
|
|
50
|
+
return undefined;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
firstTableProperty(definition: PageBlockDefinition) {
|
|
54
|
+
const property = definition.item.properties.find(p => p.type === PageBlockDefinitionPropertyType.table);
|
|
55
|
+
if (property) return property;
|
|
56
|
+
return undefined;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
richTextProperty(definition: PageBlockDefinition, propertyKey: string) {
|
|
60
|
+
return this.property(definition, propertyKey, PageBlockDefinitionPropertyType.richText);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
property(
|
|
64
|
+
definition: PageBlockDefinition,
|
|
65
|
+
propertyKey: string,
|
|
66
|
+
expectedPropertyType?: PageBlockDefinitionPropertyType
|
|
67
|
+
) {
|
|
68
|
+
const property = definition.item.properties.find(p => p.id === propertyKey);
|
|
69
|
+
if (!property) {
|
|
70
|
+
throw new Error(`Definition ${definition.id} doesn't contain property ${propertyKey}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (expectedPropertyType && property.type !== expectedPropertyType) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Property ${propertyKey} of definition ${definition.id} expected to be ${expectedPropertyType} but was ${property.type}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return property;
|
|
80
|
+
},
|
|
81
|
+
};
|
package/src/index.ts
ADDED