@witchcraft/editor 0.0.8 → 0.1.0
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/dist/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/components/CodeBlockThemePicker.d.vue.ts +4 -2
- package/dist/runtime/components/CodeBlockThemePicker.vue +0 -1
- package/dist/runtime/components/CodeBlockThemePicker.vue.d.ts +4 -2
- package/dist/runtime/components/Commands.d.vue.ts +2 -1
- package/dist/runtime/components/Commands.vue.d.ts +2 -1
- package/dist/runtime/components/Editor.d.vue.ts +3 -12
- package/dist/runtime/components/Editor.vue.d.ts +3 -12
- package/dist/runtime/components/EditorDemoApp.d.vue.ts +2 -1
- package/dist/runtime/components/EditorDemoApp.vue.d.ts +2 -1
- package/dist/runtime/components/EditorDemoControls.d.vue.ts +4 -2
- package/dist/runtime/components/EditorDemoControls.vue.d.ts +4 -2
- package/dist/runtime/components/TestWrapper.d.vue.ts +2 -1
- package/dist/runtime/components/TestWrapper.vue.d.ts +2 -1
- package/dist/runtime/demo/App.d.vue.ts +2 -1
- package/dist/runtime/demo/App.vue.d.ts +2 -1
- package/dist/runtime/pm/features/Blocks/components/DragTreeHandle.d.vue.ts +2 -1
- package/dist/runtime/pm/features/Blocks/components/DragTreeHandle.vue.d.ts +2 -1
- package/dist/runtime/pm/features/Blocks/components/ItemMenu.d.vue.ts +2 -1
- package/dist/runtime/pm/features/Blocks/components/ItemMenu.vue.d.ts +2 -1
- package/dist/runtime/pm/features/Blocks/components/ItemNodeView.d.vue.ts +2 -1
- package/dist/runtime/pm/features/Blocks/components/ItemNodeView.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CodeBlock/components/CodeBlockView.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CodeBlock/components/CodeBlockView.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandBar.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandBar.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandBarItem.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandBarItem.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandMenuGroup.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandMenuGroup.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandMenuItem.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandMenuItem.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandMenuList.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/CommandMenuList.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/TextIcon.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/components/TextIcon.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/icons/HighlightIcon.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/icons/HighlightIcon.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/icons/SubscriptIcon.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/icons/SubscriptIcon.vue.d.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/icons/SuperscriptIcon.d.vue.ts +2 -1
- package/dist/runtime/pm/features/CommandsMenus/icons/SuperscriptIcon.vue.d.ts +2 -1
- package/dist/runtime/pm/features/DocumentApi/DocumentApi.d.ts +3 -0
- package/dist/runtime/pm/features/DocumentApi/DocumentApi.js +2 -0
- package/dist/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.js +4 -1
- package/dist/runtime/pm/features/EmbeddedDocument/components/EmbeddedDocumentPicker.d.vue.ts +2 -1
- package/dist/runtime/pm/features/EmbeddedDocument/components/EmbeddedDocumentPicker.vue +3 -3
- package/dist/runtime/pm/features/EmbeddedDocument/components/EmbeddedDocumentPicker.vue.d.ts +2 -1
- package/dist/runtime/pm/features/EmbeddedDocument/components/EmbeddedNodeView.d.vue.ts +2 -1
- package/dist/runtime/pm/features/EmbeddedDocument/components/EmbeddedNodeView.vue.d.ts +2 -1
- package/dist/runtime/pm/features/FileLoader/components/FileLoaderNodeView.d.vue.ts +2 -1
- package/dist/runtime/pm/features/FileLoader/components/FileLoaderNodeView.vue.d.ts +2 -1
- package/dist/runtime/pm/features/Iframe/components/IframeNodeView.d.vue.ts +2 -1
- package/dist/runtime/pm/features/Iframe/components/IframeNodeView.vue +0 -1
- package/dist/runtime/pm/features/Iframe/components/IframeNodeView.vue.d.ts +2 -1
- package/dist/runtime/pm/features/Link/components/BubbleMenuExternalLink.d.vue.ts +4 -2
- package/dist/runtime/pm/features/Link/components/BubbleMenuExternalLink.vue.d.ts +4 -2
- package/dist/runtime/pm/features/Link/components/BubbleMenuInternalLink.d.vue.ts +4 -2
- package/dist/runtime/pm/features/Link/components/BubbleMenuInternalLink.vue.d.ts +4 -2
- package/dist/runtime/pm/features/Link/components/BubbleMenuLink.d.vue.ts +2 -1
- package/dist/runtime/pm/features/Link/components/BubbleMenuLink.vue.d.ts +2 -1
- package/dist/runtime/pm/features/Link/components/BubbleMenuLinkActions.d.vue.ts +2 -1
- package/dist/runtime/pm/features/Link/components/BubbleMenuLinkActions.vue.d.ts +2 -1
- package/dist/runtime/pm/features/Menus/components/MarkMenuManager.d.vue.ts +2 -1
- package/dist/runtime/pm/features/Menus/components/MarkMenuManager.vue.d.ts +2 -1
- package/dist/runtime/pm/generator.d.ts +30 -44
- package/dist/runtime/pm/generator.js +112 -109
- package/dist/runtime/pm/utils/generateRandomDoc.d.ts +3 -21
- package/dist/runtime/pm/utils/generateRandomDoc.js +36 -77
- package/dist/runtime/pm/utils/generator/createGeneratorConfig.d.ts +33 -0
- package/dist/runtime/pm/utils/generator/createGeneratorConfig.js +3 -0
- package/dist/runtime/pm/utils/generator/createPsuedoSentence.d.ts +4 -0
- package/dist/runtime/pm/utils/generator/createPsuedoSentence.js +16 -0
- package/dist/runtime/pm/utils/generator/createRandomChildCountGenerator.d.ts +14 -0
- package/dist/runtime/pm/utils/generator/createRandomChildCountGenerator.js +16 -0
- package/dist/runtime/pm/utils/generator/getTextOrMarkLength.d.ts +2 -0
- package/dist/runtime/pm/utils/generator/getTextOrMarkLength.js +7 -0
- package/dist/runtime/pm/utils/generator/influenceWithDepth.d.ts +6 -0
- package/dist/runtime/pm/utils/generator/influenceWithDepth.js +6 -0
- package/dist/runtime/pm/utils/generator/sometimesZero.d.ts +13 -0
- package/dist/runtime/pm/utils/generator/sometimesZero.js +7 -0
- package/package.json +59 -58
- package/src/runtime/components/CodeBlockThemePicker.vue +0 -1
- package/src/runtime/pm/features/DocumentApi/DocumentApi.ts +3 -0
- package/src/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.ts +2 -1
- package/src/runtime/pm/features/EmbeddedDocument/components/EmbeddedDocumentPicker.vue +3 -4
- package/src/runtime/pm/features/Iframe/components/IframeNodeView.vue +0 -1
- package/src/runtime/pm/generator.ts +104 -151
- package/src/runtime/pm/schema.ts +2 -2
- package/src/runtime/pm/utils/generateRandomDoc.ts +47 -124
- package/src/runtime/pm/utils/generator/createGeneratorConfig.ts +56 -0
- package/src/runtime/pm/utils/generator/createPsuedoSentence.ts +18 -0
- package/src/runtime/pm/utils/generator/createRandomChildCountGenerator.ts +31 -0
- package/src/runtime/pm/utils/generator/getTextOrMarkLength.ts +9 -0
- package/src/runtime/pm/utils/generator/influenceWithDepth.ts +11 -0
- package/src/runtime/pm/utils/generator/sometimesZero.ts +16 -0
- package/dist/runtime/pm/utils/generateRandomTree.d.ts +0 -50
- package/dist/runtime/pm/utils/generateRandomTree.js +0 -38
- package/src/runtime/pm/utils/generateRandomTree.ts +0 -100
|
@@ -1,140 +1,63 @@
|
|
|
1
|
-
import { unreachable } from "@alanscodelog/utils/unreachable"
|
|
2
1
|
import { faker } from "@faker-js/faker"
|
|
3
|
-
import type {
|
|
2
|
+
import type { Node } from "@tiptap/pm/model"
|
|
4
3
|
import type { builders } from "prosemirror-test-builder"
|
|
5
4
|
|
|
6
|
-
import {
|
|
5
|
+
import type { GeneratorConfigEntry } from "./generator/createGeneratorConfig.js"
|
|
7
6
|
|
|
8
|
-
import type { GeneratorConfigEntry } from "../generator.js"
|
|
9
7
|
import { generatorConfig } from "../generator.js"
|
|
10
8
|
|
|
11
|
-
/**
|
|
12
|
-
* Generates a random doc using faker js.
|
|
13
|
-
*
|
|
14
|
-
* A config describing how to create the doc is required. One is available under `pm/generator`.
|
|
15
|
-
*
|
|
16
|
-
* @experimental
|
|
17
|
-
*/
|
|
18
9
|
export function generateRandomDoc<
|
|
19
|
-
TBuilder extends ReturnType<typeof builders
|
|
20
|
-
TData extends string
|
|
10
|
+
TBuilder extends ReturnType<typeof builders>
|
|
21
11
|
>(
|
|
22
|
-
|
|
23
|
-
config: GeneratorConfigEntry<any, any, any, any>[] = generatorConfig
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (const node of entry.parents.names) {
|
|
49
|
-
if (node === "") throw new Error(`Empty node name ""`)
|
|
50
|
-
const type = entry.parents.type === "node" ? schema.nodes[node] : schema.marks[node]
|
|
51
|
-
if (type === undefined && node !== "text") {
|
|
52
|
-
throw new Error(`${node} (on entry of type ${entry.parents.type}) not found in schema. Valid names are ${Object.keys(entry.parents.type === "node" ? schema.nodes : schema.marks)}`)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const node of entry.parents.names) {
|
|
56
|
-
const subEntry = { ...entry }
|
|
57
|
-
if (entry.ignoreValidation === undefined || entry.ignoreValidation !== true) {
|
|
58
|
-
const childrenToValidate = (entry.children.names ?? []).filter(_ => !Array.isArray(entry.ignoreValidation) || !entry.ignoreValidation.includes(_))
|
|
59
|
-
for (const child of childrenToValidate) {
|
|
60
|
-
if (child === "") throw new Error(`Empty node name ""`)
|
|
61
|
-
if (entry.children.type === "node") {
|
|
62
|
-
const childType = schema.nodes[child]
|
|
63
|
-
const possibleNames = [childType.name, ...((childType.spec.group ?? "").split(" ").filter(e => e !== ""))]
|
|
64
|
-
const isAllowedChild = type.spec.content && possibleNames.some(n => type.spec.content!.includes(n))
|
|
65
|
-
// we don't use contentMatch.matchType because
|
|
66
|
-
// it can give false negatives when the content expression
|
|
67
|
-
// has multiple types like type1+ type2+,
|
|
68
|
-
// it will only return true for type1 in those cases
|
|
69
|
-
if (!(isAllowedChild && schemaFilter(childType.name))) {
|
|
70
|
-
throw new Error(`Node ${node} cannot contain child of type ${child}`)
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
const childType = schema.marks[child]
|
|
74
|
-
if (entry.parents.type === "node") {
|
|
75
|
-
// not sure why this is returning false when it shouldn't (e.g. paragraph can't contain bold)
|
|
76
|
-
// if (!(type as NodeType).allowsMarkType(childType)) {
|
|
77
|
-
// throw new Error(`Node ${node} cannot contain mark child of type ${child}`)
|
|
78
|
-
// }
|
|
79
|
-
} else {
|
|
80
|
-
const possibleNames = [childType.name, ...((childType.spec.group ?? "").split(" ").filter(e => e !== ""))]
|
|
81
|
-
const t = type as MarkType
|
|
82
|
-
const isExcluded = t.spec.excludes !== undefined
|
|
83
|
-
&& possibleNames.some(n => {
|
|
84
|
-
// _ this means exclude everything in prosemirror
|
|
85
|
-
return t.spec.excludes!.includes(n)
|
|
86
|
-
|| t.spec.excludes!.includes("_")
|
|
87
|
-
})
|
|
88
|
-
if (isExcluded) {
|
|
89
|
-
throw new Error(`Mark ${node} cannot contain mark child of type ${child}`)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
map[node] = { ...subEntry }
|
|
12
|
+
pm: TBuilder,
|
|
13
|
+
config: GeneratorConfigEntry<any, any, any, any>[] = generatorConfig
|
|
14
|
+
) {
|
|
15
|
+
function generateNode(
|
|
16
|
+
pm: TBuilder,
|
|
17
|
+
nodeConfig: GeneratorConfigEntry<TBuilder, any, any, any, any>,
|
|
18
|
+
parentType: string,
|
|
19
|
+
depth = 0
|
|
20
|
+
): Node {
|
|
21
|
+
if (!nodeConfig.parents.names.includes(parentType)) {
|
|
22
|
+
throw new Error(`Node config ${nodeConfig.parents.names.join(", ")} does not include parent type ${parentType}`)
|
|
23
|
+
}
|
|
24
|
+
const nodes = []
|
|
25
|
+
if ("count" in nodeConfig) {
|
|
26
|
+
const childrenCount = nodeConfig.count(depth)
|
|
27
|
+
for (let i = 0; i < childrenCount; i++) {
|
|
28
|
+
const type = pickWeightedChild(nodeConfig.children.names)
|
|
29
|
+
if (type === undefined) continue
|
|
30
|
+
if (nodeConfig?.skipChild?.(parentType, type, depth)) continue
|
|
31
|
+
const childConfig = config.find(c => {
|
|
32
|
+
return c.parents.names.includes(type)
|
|
33
|
+
})
|
|
34
|
+
if (!childConfig) continue
|
|
35
|
+
const child = generateNode(pm, childConfig, type, depth + 1)
|
|
36
|
+
if (child === undefined) continue
|
|
37
|
+
nodes.push(child)
|
|
96
38
|
}
|
|
97
39
|
}
|
|
40
|
+
return nodeConfig.create(pm, parentType, nodes)
|
|
98
41
|
}
|
|
42
|
+
const firstEntry = config[0]
|
|
43
|
+
if (firstEntry === undefined) throw new Error("No config entries found.")
|
|
44
|
+
if (!("children" in firstEntry)) throw new Error("No children found in the first config entry. The first entry must have children.")
|
|
45
|
+
const startingType = pickWeightedChild(firstEntry.parents.names.map(name => [name, 1]))
|
|
46
|
+
if (startingType === undefined) throw new Error("No starting type found.")
|
|
47
|
+
return generateNode(pm, config[0], startingType)
|
|
48
|
+
}
|
|
99
49
|
|
|
100
50
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!data) unreachable()
|
|
105
|
-
if (!map[data] || !map[data].children.names || map[data].children.names.length === 0) {
|
|
106
|
-
// we must prematurely end the branch as the node can contain no children
|
|
107
|
-
return "" as TData
|
|
108
|
-
} else {
|
|
109
|
-
const picked = faker.helpers.arrayElement(map[data].children.names)
|
|
110
|
-
return picked as TData
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
createNode: (children, _isLeaf, data): Node | Mark | string | undefined => {
|
|
114
|
-
if (data === "") return undefined
|
|
115
|
-
if (!data) unreachable()
|
|
116
|
-
const type = schema.nodes[data]
|
|
117
|
-
// type allows no children or isn't configured to generate children
|
|
118
|
-
if (!type) return undefined
|
|
119
|
-
|
|
120
|
-
const parent = map[data]?.create?.(data, children as any)
|
|
121
|
-
?? builder[data]({}, ...children as any)
|
|
122
|
-
|
|
123
|
-
if (checkAfterNodeCreation) {
|
|
124
|
-
;(parent as any)?.check?.()
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return parent
|
|
128
|
-
}
|
|
129
|
-
}, {
|
|
130
|
-
...treeOptions,
|
|
131
|
-
initialData: schema.topNodeType.name as TData
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
const doc = map[schema.topNodeType.name]?.create?.(schema.topNodeType.name as any, children as any)
|
|
135
|
-
?? builder[schema.topNodeType.name]({}, ...children as any)
|
|
51
|
+
export function pickWeightedChild(options: [string, number][]): string | undefined {
|
|
52
|
+
const totalWeight = options.reduce((sum, [, weight]) => sum + weight, 0)
|
|
53
|
+
if (totalWeight === 0) return undefined
|
|
136
54
|
|
|
137
|
-
|
|
138
|
-
|
|
55
|
+
let random = faker.number.float({ min: 0, max: totalWeight })
|
|
56
|
+
for (const [name, weight] of options) {
|
|
57
|
+
if (random < weight) return name
|
|
58
|
+
random -= weight
|
|
59
|
+
}
|
|
60
|
+
// Fallback to first if rounding errors occur
|
|
61
|
+
return options[0][0]
|
|
139
62
|
}
|
|
140
63
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Mark, Node } from "@tiptap/pm/model"
|
|
2
|
+
import type { builders } from "prosemirror-test-builder"
|
|
3
|
+
|
|
4
|
+
export function createGeneratorConfig<
|
|
5
|
+
TBuilder extends ReturnType<typeof builders>,
|
|
6
|
+
TChildType extends "node" | "text",
|
|
7
|
+
TParentType extends "node" | "text",
|
|
8
|
+
TChildNodeType extends TChildType extends "node" ? Node : (Mark | string),
|
|
9
|
+
TParentNodeType extends TParentType extends "node" ? Node : (Mark | string)
|
|
10
|
+
>(
|
|
11
|
+
config: GeneratorConfigEntry<TBuilder, TChildType, TParentType, TChildNodeType, TParentNodeType>
|
|
12
|
+
): GeneratorConfigEntry<TBuilder, TChildType, TParentType, TChildNodeType, TParentNodeType> {
|
|
13
|
+
return config
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type GeneratorConfigEntry<
|
|
17
|
+
TBuilder extends ReturnType<typeof builders>,
|
|
18
|
+
TChildType extends "node" | "text" = "node" | "text",
|
|
19
|
+
TParentType extends "node" | "text" = "node" | "text",
|
|
20
|
+
TChildNodeType extends TChildType extends "node" ? Node : (Mark | string) = TChildType extends "node" ? Node : (Mark | string),
|
|
21
|
+
TParentNodeType extends TParentType extends "node" ? Node : (Mark | string) = TParentType extends "node" ? Node : (Mark | string)
|
|
22
|
+
> = {
|
|
23
|
+
/**
|
|
24
|
+
* Creates the node. Can return undefined to "terminate" the branch being created, the node will be filtered out of the nodes passed to it's parent.
|
|
25
|
+
*
|
|
26
|
+
* Note also, it is not required to use the children. You can ignore them or use a subset or create different ones if needed (some node types *require* text and if count returns 0 children will be empty, which can cause issues).
|
|
27
|
+
*/
|
|
28
|
+
create: (pm: TBuilder, parentType: string, children: TChildNodeType[]) => TParentNodeType | undefined
|
|
29
|
+
parents: {
|
|
30
|
+
/**
|
|
31
|
+
* The type of the parent (node or "text") where "text" is a string or mark.
|
|
32
|
+
* Determines the call signature of the create function.
|
|
33
|
+
*/
|
|
34
|
+
type: TParentType
|
|
35
|
+
/** Possible parent nodes that can have the given children. The children need not be direct descendants or real prosemirror nodes since `create` is what determines how they're created. */
|
|
36
|
+
names: string[]
|
|
37
|
+
}
|
|
38
|
+
} & (
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
40
|
+
{} | {
|
|
41
|
+
skipChild?: (parentType: string, childType: string, depth: number) => boolean
|
|
42
|
+
children: {
|
|
43
|
+
/** The type of children (node or "text") where "text" is a string or mark. */
|
|
44
|
+
type: TChildType
|
|
45
|
+
/* Children types and their relative weights. */
|
|
46
|
+
names: [name: string, weight: number][]
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* How many children to generate, is passed the depth so you can make the tree "slimmer" as it does deeper.
|
|
50
|
+
*
|
|
51
|
+
* See the `sometimesZero`, `influenceWithDepth` and `createRandomChildCountGenerator` helpers to make this easier to control.
|
|
52
|
+
*/
|
|
53
|
+
count: (depth: number) => number
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { faker } from "@faker-js/faker"
|
|
2
|
+
|
|
3
|
+
export function createPsuedoSentence({ min = 0, max = 10}: { min?: number, max?: number } = {}) {
|
|
4
|
+
// sentence generated with string.sample (which contains all possible chars) instead of lorem.sentence
|
|
5
|
+
const sentenceLength = faker.number.int({ min, max })
|
|
6
|
+
const sentence = Array.from(
|
|
7
|
+
{ length: sentenceLength },
|
|
8
|
+
() => {
|
|
9
|
+
const doLorem = faker.number.float() < 0.5
|
|
10
|
+
if (doLorem) {
|
|
11
|
+
return faker.lorem.word()
|
|
12
|
+
} else {
|
|
13
|
+
return faker.string.sample({ min: 1, max: 10 })
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
).join(" ")
|
|
17
|
+
return sentence
|
|
18
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { faker } from "@faker-js/faker"
|
|
2
|
+
|
|
3
|
+
import { influenceWithDepth } from "./influenceWithDepth.js"
|
|
4
|
+
import { sometimesZero } from "./sometimesZero.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Combines `sometimesZero` and `influenceWithDepth` to create a function that returns a random number to pass to `count`.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* count: createRandomChildCountGenerator({ max: 10, depthInfluence: 0.5 })
|
|
11
|
+
* // or
|
|
12
|
+
* count: (depth: number) => createRandomChildCountGenerator({ max: 10, depthInfluence: 0.5 })(depth)
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function createRandomChildCountGenerator({
|
|
16
|
+
max = 10,
|
|
17
|
+
zeroChance = 0.05,
|
|
18
|
+
depthInfluence = 1
|
|
19
|
+
}: {
|
|
20
|
+
max: number
|
|
21
|
+
zeroChance?: number
|
|
22
|
+
depthInfluence?: number
|
|
23
|
+
}) {
|
|
24
|
+
return (depth: number) => {
|
|
25
|
+
depth++
|
|
26
|
+
return sometimesZero(
|
|
27
|
+
faker.number.int({ min: 1, max: influenceWithDepth(max, depth, depthInfluence) || 1 }),
|
|
28
|
+
zeroChance
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Mark } from "@tiptap/pm/model"
|
|
2
|
+
|
|
3
|
+
export function getTextOrMarkLength(textOrMark: string | Mark) {
|
|
4
|
+
if (typeof textOrMark === "string") {
|
|
5
|
+
return textOrMark.length
|
|
6
|
+
}
|
|
7
|
+
const textNodes = (textOrMark as any).flat.map((_: any) => "text" in _ ? _.text.length : getTextOrMarkLength(_ as any)) as number[]
|
|
8
|
+
return textNodes.reduce((a, b) => a + b, 0)
|
|
9
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decreases the given number by dividing it by the depth + 1 (so it it's not zero). Returns a floored *int*.
|
|
3
|
+
*
|
|
4
|
+
* Intended to help decrease the number of nodes as the depth increases. Can be adjusted by the strength parameter, to, for example, decrease the adjustment for nodes that tend to be deeper.
|
|
5
|
+
*/
|
|
6
|
+
export function influenceWithDepth(num: number, depth: number, strength: number = 1) {
|
|
7
|
+
if (num === 0) return 0
|
|
8
|
+
depth++ // don't allow it to be 0
|
|
9
|
+
const adjusted = Math.floor(num / (depth * strength))
|
|
10
|
+
return adjusted
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { faker } from "@faker-js/faker"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns 0 instead of the given number depending on the chance passed (0.05 by default).
|
|
5
|
+
*
|
|
6
|
+
* This is useful to avoid too many empty nodes without having to increase the max a lot.
|
|
7
|
+
*
|
|
8
|
+
* You can pass chance 0 to always return the given number.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function sometimesZero(num: number, chance: number = 0.05, getRandom = faker.number.float) {
|
|
12
|
+
if (getRandom() < chance) {
|
|
13
|
+
return 0
|
|
14
|
+
}
|
|
15
|
+
return num
|
|
16
|
+
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates a random tree structure where the probability of generating children
|
|
3
|
-
* decreases linearly with depth, resulting in a tree that is bushy at the top
|
|
4
|
-
* and sparse towards the maximum depth.
|
|
5
|
-
*
|
|
6
|
-
* This function is agnostic to the actual node structure, relying on the caller-provided
|
|
7
|
-
* callbacks to create and link nodes.
|
|
8
|
-
*
|
|
9
|
-
* It uses faker.js for randomness (even on the option parameters).
|
|
10
|
-
*
|
|
11
|
-
* See {@link generateRandomDoc} for an example of how to use it.
|
|
12
|
-
*
|
|
13
|
-
* @experimental
|
|
14
|
-
*/
|
|
15
|
-
export declare function generateRandomTree<T, TData>({ createNode, parentData }: {
|
|
16
|
-
/** A function that creates a new node (type T) given its depth. */
|
|
17
|
-
createNode: (children: T[], isLeaf: boolean, parentData?: TData) => T | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* A function that is called before creating a a node or it's children whose result is passed to both. This allows creating children that will be compatible with the parent (via this same function, it is passed it's parent).
|
|
20
|
-
*
|
|
21
|
-
* For example, we could randomly generate a parent type. It's then passed both to the children, to limit the types of children nodes, and to the parent so it can actually create it.
|
|
22
|
-
*/
|
|
23
|
-
parentData?: (parentData?: TData) => TData;
|
|
24
|
-
}, { rootNodes: rootNodes, depth, minChildren, maxInitialChildren, initialData }?: {
|
|
25
|
-
/**
|
|
26
|
-
* The exact number of nodes at depth 0.
|
|
27
|
-
*
|
|
28
|
-
* @default faker.number.int({ min: 0, max: 5 })
|
|
29
|
-
*/
|
|
30
|
-
rootNodes?: number;
|
|
31
|
-
/**
|
|
32
|
-
* The maximum depth of the tree (0-indexed). Nodes at this depth will have 0 children.
|
|
33
|
-
*
|
|
34
|
-
* @default faker.number.int({ min: 0, max: 5 })
|
|
35
|
-
*/
|
|
36
|
-
depth?: number;
|
|
37
|
-
/**
|
|
38
|
-
* The absolute minimum number of children any node can have.
|
|
39
|
-
*
|
|
40
|
-
* @default 0
|
|
41
|
-
*/
|
|
42
|
-
minChildren?: number;
|
|
43
|
-
/**
|
|
44
|
-
* The maximum children a node can have at depth 0. This value scales down as depth increases.
|
|
45
|
-
*
|
|
46
|
-
* @default 10
|
|
47
|
-
*/
|
|
48
|
-
maxInitialChildren?: number;
|
|
49
|
-
initialData?: TData;
|
|
50
|
-
}): T[];
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { faker } from "@faker-js/faker";
|
|
2
|
-
export function generateRandomTree({
|
|
3
|
-
createNode,
|
|
4
|
-
parentData
|
|
5
|
-
}, {
|
|
6
|
-
rootNodes = faker.number.int({ min: 0, max: 5 }),
|
|
7
|
-
depth = faker.number.int({ min: 0, max: 5 }),
|
|
8
|
-
minChildren = 0,
|
|
9
|
-
maxInitialChildren = 10,
|
|
10
|
-
initialData
|
|
11
|
-
} = {}) {
|
|
12
|
-
const generateChildren = (currentDepth, childCount, pData) => {
|
|
13
|
-
const res = [];
|
|
14
|
-
for (let i = 0; i < childCount; i++) {
|
|
15
|
-
const numChildren = calculateNumChildren(depth, currentDepth, minChildren, maxInitialChildren);
|
|
16
|
-
const data = parentData?.(pData) ?? void 0;
|
|
17
|
-
const parent = createNode(
|
|
18
|
-
generateChildren(currentDepth + 1, numChildren, data),
|
|
19
|
-
numChildren === 0,
|
|
20
|
-
data
|
|
21
|
-
);
|
|
22
|
-
if (parent === void 0) continue;
|
|
23
|
-
res.push(parent);
|
|
24
|
-
}
|
|
25
|
-
return res;
|
|
26
|
-
};
|
|
27
|
-
return generateChildren(0, rootNodes, initialData);
|
|
28
|
-
}
|
|
29
|
-
function calculateNumChildren(depth, currentDepth, minChildren, maxInitialChildren) {
|
|
30
|
-
const decayFactor = depth > 0 ? (depth - currentDepth) / depth : 0;
|
|
31
|
-
const maxAtDepth = minChildren + (maxInitialChildren - minChildren) * decayFactor;
|
|
32
|
-
const maxChildrenAtDepth = Math.max(minChildren, Math.floor(maxAtDepth));
|
|
33
|
-
let numChildren = 0;
|
|
34
|
-
if (currentDepth < depth) {
|
|
35
|
-
numChildren = faker.number.int({ min: minChildren, max: maxChildrenAtDepth });
|
|
36
|
-
}
|
|
37
|
-
return numChildren;
|
|
38
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { faker } from "@faker-js/faker"
|
|
2
|
-
/**
|
|
3
|
-
* Generates a random tree structure where the probability of generating children
|
|
4
|
-
* decreases linearly with depth, resulting in a tree that is bushy at the top
|
|
5
|
-
* and sparse towards the maximum depth.
|
|
6
|
-
*
|
|
7
|
-
* This function is agnostic to the actual node structure, relying on the caller-provided
|
|
8
|
-
* callbacks to create and link nodes.
|
|
9
|
-
*
|
|
10
|
-
* It uses faker.js for randomness (even on the option parameters).
|
|
11
|
-
*
|
|
12
|
-
* See {@link generateRandomDoc} for an example of how to use it.
|
|
13
|
-
*
|
|
14
|
-
* @experimental
|
|
15
|
-
*/
|
|
16
|
-
export function generateRandomTree<T, TData>(
|
|
17
|
-
{
|
|
18
|
-
createNode,
|
|
19
|
-
parentData
|
|
20
|
-
}: {
|
|
21
|
-
/** A function that creates a new node (type T) given its depth. */
|
|
22
|
-
createNode: (children: T[], isLeaf: boolean, parentData?: TData) => T | undefined
|
|
23
|
-
/**
|
|
24
|
-
* A function that is called before creating a a node or it's children whose result is passed to both. This allows creating children that will be compatible with the parent (via this same function, it is passed it's parent).
|
|
25
|
-
*
|
|
26
|
-
* For example, we could randomly generate a parent type. It's then passed both to the children, to limit the types of children nodes, and to the parent so it can actually create it.
|
|
27
|
-
*/
|
|
28
|
-
parentData?: (parentData?: TData) => TData
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
rootNodes: rootNodes = faker.number.int({ min: 0, max: 5 }),
|
|
32
|
-
depth = faker.number.int({ min: 0, max: 5 }),
|
|
33
|
-
minChildren = 0,
|
|
34
|
-
maxInitialChildren = 10,
|
|
35
|
-
initialData
|
|
36
|
-
}: {
|
|
37
|
-
/**
|
|
38
|
-
* The exact number of nodes at depth 0.
|
|
39
|
-
*
|
|
40
|
-
* @default faker.number.int({ min: 0, max: 5 })
|
|
41
|
-
*/
|
|
42
|
-
rootNodes?: number
|
|
43
|
-
/**
|
|
44
|
-
* The maximum depth of the tree (0-indexed). Nodes at this depth will have 0 children.
|
|
45
|
-
*
|
|
46
|
-
* @default faker.number.int({ min: 0, max: 5 })
|
|
47
|
-
*/
|
|
48
|
-
depth?: number
|
|
49
|
-
/**
|
|
50
|
-
* The absolute minimum number of children any node can have.
|
|
51
|
-
*
|
|
52
|
-
* @default 0
|
|
53
|
-
*/
|
|
54
|
-
minChildren?: number
|
|
55
|
-
/**
|
|
56
|
-
* The maximum children a node can have at depth 0. This value scales down as depth increases.
|
|
57
|
-
*
|
|
58
|
-
* @default 10
|
|
59
|
-
*/
|
|
60
|
-
maxInitialChildren?: number
|
|
61
|
-
initialData?: TData
|
|
62
|
-
} = {}
|
|
63
|
-
): T[] {
|
|
64
|
-
const generateChildren = (currentDepth: number, childCount: number, pData?: TData): T[] => {
|
|
65
|
-
const res: T[] = []
|
|
66
|
-
for (let i = 0; i < childCount; i++) {
|
|
67
|
-
const numChildren = calculateNumChildren(depth, currentDepth, minChildren, maxInitialChildren)
|
|
68
|
-
|
|
69
|
-
const data = parentData?.(pData) ?? undefined
|
|
70
|
-
const parent = createNode(
|
|
71
|
-
generateChildren(currentDepth + 1, numChildren, data),
|
|
72
|
-
numChildren === 0,
|
|
73
|
-
data
|
|
74
|
-
)
|
|
75
|
-
if (parent === undefined) continue
|
|
76
|
-
res.push(parent)
|
|
77
|
-
}
|
|
78
|
-
return res
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return generateChildren(0, rootNodes, initialData)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function calculateNumChildren(
|
|
85
|
-
depth: number,
|
|
86
|
-
currentDepth: number,
|
|
87
|
-
minChildren: number,
|
|
88
|
-
maxInitialChildren: number
|
|
89
|
-
): number {
|
|
90
|
-
const decayFactor = depth > 0 ? (depth - currentDepth) / depth : 0
|
|
91
|
-
const maxAtDepth = minChildren + (maxInitialChildren - minChildren) * decayFactor
|
|
92
|
-
const maxChildrenAtDepth = Math.max(minChildren, Math.floor(maxAtDepth))
|
|
93
|
-
|
|
94
|
-
let numChildren = 0
|
|
95
|
-
|
|
96
|
-
if (currentDepth < depth) {
|
|
97
|
-
numChildren = faker.number.int({ min: minChildren, max: maxChildrenAtDepth })
|
|
98
|
-
}
|
|
99
|
-
return numChildren
|
|
100
|
-
}
|