@witchcraft/editor 0.0.6 → 0.0.8

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.
Files changed (59) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/CodeBlockThemePicker.vue +0 -1
  3. package/dist/runtime/components/Commands.vue +1 -1
  4. package/dist/runtime/components/Editor.vue +1 -2
  5. package/dist/runtime/composables/useEditor.d.ts +1 -1
  6. package/dist/runtime/demo/App.vue +0 -1
  7. package/dist/runtime/pm/features/Blockquote/Blockquote.d.ts +1 -1
  8. package/dist/runtime/pm/features/Blocks/components/DragTreeHandle.d.vue.ts +26 -0
  9. package/dist/runtime/pm/features/Blocks/components/DragTreeHandle.vue +7 -0
  10. package/dist/runtime/pm/features/Blocks/components/DragTreeHandle.vue.d.ts +26 -0
  11. package/dist/runtime/pm/features/Blocks/components/ItemMenu.vue +1 -2
  12. package/dist/runtime/pm/features/Blocks/components/ItemNodeView.vue +0 -1
  13. package/dist/runtime/pm/features/Blocks/components/defaultItemMenu.d.ts +2 -2
  14. package/dist/runtime/pm/features/Blocks/composables/useNodeStates.d.ts +2 -2
  15. package/dist/runtime/pm/features/CodeBlock/CodeBlock.d.ts +1 -1
  16. package/dist/runtime/pm/features/CodeBlock/components/CodeBlockView.vue +0 -1
  17. package/dist/runtime/pm/features/CodeBlock/composables/useHighlightJsTheme.d.ts +3 -3
  18. package/dist/runtime/pm/features/CommandsMenus/components/CommandBar.vue +1 -1
  19. package/dist/runtime/pm/features/CommandsMenus/components/CommandBarItem.vue +1 -1
  20. package/dist/runtime/pm/features/CommandsMenus/components/CommandMenuGroup.vue +0 -1
  21. package/dist/runtime/pm/features/CommandsMenus/icons/HighlightIcon.vue +1 -1
  22. package/dist/runtime/pm/features/EmbeddedDocument/components/EmbeddedDocumentPicker.vue +0 -1
  23. package/dist/runtime/pm/features/EmbeddedDocument/components/EmbeddedNodeView.vue +0 -1
  24. package/dist/runtime/pm/features/FileLoader/components/FileLoaderNodeView.vue +0 -1
  25. package/dist/runtime/pm/features/HardBreak/HardBreak.d.ts +1 -1
  26. package/dist/runtime/pm/features/Link/components/BubbleMenuExternalLink.vue +1 -1
  27. package/dist/runtime/pm/features/Link/components/BubbleMenuInternalLink.vue +1 -1
  28. package/dist/runtime/pm/features/Link/components/BubbleMenuLink.vue +1 -1
  29. package/dist/runtime/pm/features/Menus/components/MarkMenuManager.vue +1 -1
  30. package/dist/runtime/pm/features/Tables/index.d.ts +5 -5
  31. package/dist/runtime/pm/generator.d.ts +82 -0
  32. package/dist/runtime/pm/generator.js +205 -0
  33. package/dist/runtime/pm/testSchema.d.ts +1 -1
  34. package/dist/runtime/pm/utils/generateRandomDoc.d.ts +23 -0
  35. package/dist/runtime/pm/utils/generateRandomDoc.js +83 -0
  36. package/dist/runtime/pm/utils/generateRandomTree.d.ts +50 -0
  37. package/dist/runtime/pm/utils/generateRandomTree.js +38 -0
  38. package/package.json +7 -5
  39. package/src/module.ts +1 -0
  40. package/src/runtime/components/CodeBlockThemePicker.vue +0 -1
  41. package/src/runtime/components/Editor.vue +0 -1
  42. package/src/runtime/demo/App.vue +0 -1
  43. package/src/runtime/pm/commands/changeAttrs.ts +1 -1
  44. package/src/runtime/pm/features/Blocks/Item.ts +1 -1
  45. package/src/runtime/pm/features/Blocks/commands/moveItem.ts +1 -1
  46. package/src/runtime/pm/features/Blocks/components/DragTreeHandle.vue +18 -14
  47. package/src/runtime/pm/features/Blocks/components/ItemMenu.vue +0 -1
  48. package/src/runtime/pm/features/Blocks/components/ItemNodeView.vue +0 -1
  49. package/src/runtime/pm/features/CodeBlock/components/CodeBlockView.vue +0 -1
  50. package/src/runtime/pm/features/CommandsMenus/components/CommandMenuGroup.vue +0 -1
  51. package/src/runtime/pm/features/EmbeddedDocument/Embedded.ts +1 -1
  52. package/src/runtime/pm/features/EmbeddedDocument/components/EmbeddedDocumentPicker.vue +0 -1
  53. package/src/runtime/pm/features/EmbeddedDocument/components/EmbeddedNodeView.vue +0 -1
  54. package/src/runtime/pm/features/FileLoader/components/FileLoaderNodeView.vue +0 -1
  55. package/src/runtime/pm/features/FileLoader/types.ts +2 -2
  56. package/src/runtime/pm/generator.ts +266 -0
  57. package/src/runtime/pm/schema.ts +1 -0
  58. package/src/runtime/pm/utils/generateRandomDoc.ts +140 -0
  59. package/src/runtime/pm/utils/generateRandomTree.ts +100 -0
@@ -0,0 +1,23 @@
1
+ import type { Node } from "@tiptap/pm/model";
2
+ import type { builders } from "prosemirror-test-builder";
3
+ import { generateRandomTree } from "./generateRandomTree.js";
4
+ import type { GeneratorConfigEntry } from "../generator.js";
5
+ /**
6
+ * Generates a random doc using faker js.
7
+ *
8
+ * A config describing how to create the doc is required. One is available under `pm/generator`.
9
+ *
10
+ * @experimental
11
+ */
12
+ export declare function generateRandomDoc<TBuilder extends ReturnType<typeof builders>, TData extends string>(builder: TBuilder, config?: GeneratorConfigEntry<any, any, any, any>[], treeOptions?: Parameters<typeof generateRandomTree>[1], { checkAfterNodeCreation }?: {
13
+ /**
14
+ * Checks if every returned node is valid for debugging invalid generator configs.
15
+ *
16
+ * A check is always done on the root node at the end regardless of this option.
17
+ *
18
+ * It is very slow (each check, rechecks children).
19
+ *
20
+ * @default false
21
+ */
22
+ checkAfterNodeCreation?: boolean;
23
+ }): Node;
@@ -0,0 +1,83 @@
1
+ import { unreachable } from "@alanscodelog/utils/unreachable";
2
+ import { faker } from "@faker-js/faker";
3
+ import { generateRandomTree } from "./generateRandomTree.js";
4
+ import { generatorConfig } from "../generator.js";
5
+ export function generateRandomDoc(builder, config = generatorConfig, treeOptions, {
6
+ checkAfterNodeCreation = false
7
+ } = {}) {
8
+ const schema = builder.schema;
9
+ const map = {};
10
+ function schemaFilter(name) {
11
+ return name !== "text" && name !== schema.topNodeType.name;
12
+ }
13
+ for (const entry of config) {
14
+ for (const node of entry.parents.names) {
15
+ if (node === "") throw new Error(`Empty node name ""`);
16
+ const type = entry.parents.type === "node" ? schema.nodes[node] : schema.marks[node];
17
+ if (type === void 0 && node !== "text") {
18
+ 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)}`);
19
+ }
20
+ for (const node2 of entry.parents.names) {
21
+ const subEntry = { ...entry };
22
+ if (entry.ignoreValidation === void 0 || entry.ignoreValidation !== true) {
23
+ const childrenToValidate = (entry.children.names ?? []).filter((_) => !Array.isArray(entry.ignoreValidation) || !entry.ignoreValidation.includes(_));
24
+ for (const child of childrenToValidate) {
25
+ if (child === "") throw new Error(`Empty node name ""`);
26
+ if (entry.children.type === "node") {
27
+ const childType = schema.nodes[child];
28
+ const possibleNames = [childType.name, ...(childType.spec.group ?? "").split(" ").filter((e) => e !== "")];
29
+ const isAllowedChild = type.spec.content && possibleNames.some((n) => type.spec.content.includes(n));
30
+ if (!(isAllowedChild && schemaFilter(childType.name))) {
31
+ throw new Error(`Node ${node2} cannot contain child of type ${child}`);
32
+ }
33
+ } else {
34
+ const childType = schema.marks[child];
35
+ if (entry.parents.type === "node") {
36
+ } else {
37
+ const possibleNames = [childType.name, ...(childType.spec.group ?? "").split(" ").filter((e) => e !== "")];
38
+ const t = type;
39
+ const isExcluded = t.spec.excludes !== void 0 && possibleNames.some((n) => {
40
+ return t.spec.excludes.includes(n) || t.spec.excludes.includes("_");
41
+ });
42
+ if (isExcluded) {
43
+ throw new Error(`Mark ${node2} cannot contain mark child of type ${child}`);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ map[node2] = { ...subEntry };
50
+ }
51
+ }
52
+ }
53
+ const children = generateRandomTree({
54
+ parentData: (data) => {
55
+ if (data === "") return "";
56
+ if (!data) unreachable();
57
+ if (!map[data] || !map[data].children.names || map[data].children.names.length === 0) {
58
+ return "";
59
+ } else {
60
+ const picked = faker.helpers.arrayElement(map[data].children.names);
61
+ return picked;
62
+ }
63
+ },
64
+ createNode: (children2, _isLeaf, data) => {
65
+ if (data === "") return void 0;
66
+ if (!data) unreachable();
67
+ const type = schema.nodes[data];
68
+ if (!type) return void 0;
69
+ const parent = map[data]?.create?.(data, children2) ?? builder[data]({}, ...children2);
70
+ if (checkAfterNodeCreation) {
71
+ ;
72
+ parent?.check?.();
73
+ }
74
+ return parent;
75
+ }
76
+ }, {
77
+ ...treeOptions,
78
+ initialData: schema.topNodeType.name
79
+ });
80
+ const doc = map[schema.topNodeType.name]?.create?.(schema.topNodeType.name, children) ?? builder[schema.topNodeType.name]({}, ...children);
81
+ doc.check?.();
82
+ return doc;
83
+ }
@@ -0,0 +1,50 @@
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[];
@@ -0,0 +1,38 @@
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
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@witchcraft/editor",
3
3
  "description": "Block base prosemirror editor with partial/full editable document embeds, infinite embeds, and document uploads.",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "main": "./dist/runtime/main.lib.js",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -110,11 +110,13 @@
110
110
  "@alanscodelog/eslint-config": "^6.3.1",
111
111
  "@alanscodelog/utils": "^6.0.2",
112
112
  "@commitlint/cli": "^19.8.1",
113
+ "@faker-js/faker": "^10.0.0",
113
114
  "@fortawesome/fontawesome-svg-core": "^7.0.1",
114
115
  "@fortawesome/free-brands-svg-icons": "^7.0.1",
115
116
  "@fortawesome/free-regular-svg-icons": "^7.0.1",
116
117
  "@fortawesome/free-solid-svg-icons": "^7.0.1",
117
118
  "@nuxt/eslint-config": "^1.9.0",
119
+ "@tiptap/html": "^3.4.2",
118
120
  "@witchcraft/nuxt-utils": "^0.3.6",
119
121
  "@witchcraft/ui": "^0.3.7",
120
122
  "colord": "^2.9.3",
@@ -141,7 +143,7 @@
141
143
  "@nuxt/module-builder": "^1.0.2",
142
144
  "@nuxt/schema": "^4.1.2",
143
145
  "@nuxt/types": "^2.18.1",
144
- "@playwright/test": "^1.54.0",
146
+ "@playwright/test": "=1.56.0",
145
147
  "@rollup/plugin-dynamic-import-vars": "^2.1.5",
146
148
  "@tailwindcss/cli": "^4.1.13",
147
149
  "@tailwindcss/vite": "^4.1.13",
@@ -153,15 +155,15 @@
153
155
  "@witchcraft/ui": "^0.3.7",
154
156
  "concurrently": "^9.2.1",
155
157
  "cross-env": "^10.0.0",
156
- "eslint": "^9.35.0",
158
+ "eslint": "^9.38.0",
157
159
  "fast-glob": "^3.3.3",
158
160
  "http-server": "^14.1.1",
159
161
  "husky": "^9.1.7",
160
162
  "madge": "^8.0.0",
161
163
  "nuxt": "^4.1.2",
162
164
  "onchange": "^7.1.0",
163
- "playwright": "^1.54.0",
164
- "playwright-core": "^1.54.0",
165
+ "playwright": "=1.56.0",
166
+ "playwright-core": "=1.56.0",
165
167
  "prosemirror-test-builder": "^1.1.1",
166
168
  "radix-vue": "^1.9.17",
167
169
  "semantic-release": "^24.2.8",
package/src/module.ts CHANGED
@@ -89,6 +89,7 @@ export default defineNuxtModule<ModuleOptions>({
89
89
  nuxt.options.build.transpile.push("@tiptap/pm")
90
90
 
91
91
  nuxt.hook("vite:extendConfig", config => {
92
+ // @ts-expect-error - optimizeDeps is now readonly but also possibly undefined :/
92
93
  config.optimizeDeps ??= {}
93
94
  config.optimizeDeps.exclude ??= []
94
95
  // causes issues with the import.meta.globs here
@@ -42,7 +42,6 @@ const codeBlocksTheme = defineModel<string>("codeBlocksTheme", { required: true
42
42
  gap-2
43
43
  "
44
44
  >
45
- <!-- @vue-expect-error -->
46
45
  <ComboboxInput
47
46
  class="
48
47
  bg-inherit
@@ -26,7 +26,6 @@
26
26
  v-bind="$attrs"
27
27
  >
28
28
  <!-- The class `is-embedded-block` is not needed internally, but is added for consistency in case it might be useful. -->
29
- <!-- @vue-expect-error -->
30
29
  <editor-content
31
30
  :editor="editor! as any"
32
31
  spellcheck="false"
@@ -1,6 +1,5 @@
1
1
  <template>
2
2
  <WRoot class="items-center py-4">
3
- <!-- @vue-expect-error use unhead in a real app -->
4
3
  <style>
5
4
  {{ codeBlocksThemeCss.join("\n") }}
6
5
  </style>
@@ -9,7 +9,7 @@ declare module "@tiptap/core" {
9
9
  changeAttrs: {
10
10
  changeAttrs: (
11
11
  nodeType: string | undefined,
12
- attrs: Record<string, unknown>,
12
+ attrs: Record<string, unknown>
13
13
  ) => ReturnType
14
14
  }
15
15
  }
@@ -32,7 +32,6 @@ declare module "@tiptap/core" {
32
32
  export const Item = Node.create<ItemNodeOptions>({
33
33
  name: "item" satisfies NodeItemName,
34
34
  content: "block list? | list",
35
-
36
35
  addOptions() {
37
36
  return {
38
37
  HTMLAttributes: {},
@@ -174,3 +173,4 @@ export const Item = Node.create<ItemNodeOptions>({
174
173
  }
175
174
  })
176
175
  export type NodeItemName = "item"
176
+
@@ -17,7 +17,7 @@ declare module "@tiptap/core" {
17
17
  */
18
18
  moveItem: (
19
19
  dir: "down" | "up",
20
- pos?: number,
20
+ pos?: number
21
21
  ) => ReturnType
22
22
  }
23
23
  }
@@ -97,27 +97,31 @@
97
97
  </div>
98
98
  </template>
99
99
 
100
- <!--
101
- Multipurpose drag handle + collapse indicator.
102
-
103
- This is incredibly useful for making a compact draggable tree view.
104
-
105
- The collapse indicator has a default height, but it should be set manually. For example `[&>.collapse-indicator]:h-[...]`
106
-
107
- The component only emits a few events, it does not handle the dragging itself or what actually happens on any clicks/input.
108
- -->
109
100
  <script lang="ts">
101
+ /**
102
+ * Multipurpose drag handle + collapse indicator.
103
+ *
104
+ * This is incredibly useful for making a compact draggable tree view.
105
+ *
106
+ * The collapse indicator has a default height, but it should be set manually. For example `[&>.collapse-indicator]:h-[...]`
107
+ *
108
+ * The component only emits a few events, it does not handle the dragging itself or what actually happens on any clicks/input.
109
+ */
110
+ interface Props {
111
+ hasChildren: boolean
112
+ passedDragThreshold: boolean
113
+ hideChildren: boolean
114
+ }
110
115
  </script>
111
116
 
112
117
  <script setup lang="ts">
113
118
  import { twMerge } from "@witchcraft/ui/utils/twMerge"
114
119
  import { onMounted, onUnmounted, ref, useAttrs } from "vue"
115
120
 
116
- interface Props {
117
- hasChildren: boolean
118
- passedDragThreshold: boolean
119
- hideChildren: boolean
120
- }
121
+
122
+ defineOptions({
123
+ name: "DragTreeHandle"
124
+ })
121
125
 
122
126
  const $attrs = useAttrs()
123
127
  defineProps<Props>()
@@ -1,5 +1,4 @@
1
1
  <template>
2
- <!-- @vue-expect-error -->
3
2
  <WPopup
4
3
  :model-value="menu.opened"
5
4
  :preferred-horizontal="['left-most']"
@@ -12,7 +12,6 @@
12
12
  --pmNodeTypeMargin
13
13
  --pmDragDropIndicatorHeight
14
14
  -->
15
- <!-- @vue-expect-error role is a valid attribute -->
16
15
  <node-view-wrapper
17
16
  :class="twMerge(`
18
17
  group/item
@@ -44,7 +44,6 @@
44
44
  />
45
45
  </div>
46
46
 
47
- <!-- @vue-expect-error -->
48
47
  <node-view-content
49
48
  :class="twMerge(`
50
49
  hljs
@@ -8,7 +8,6 @@
8
8
  :item="item"
9
9
  v-extract-root-el="(_: any) => el = _"
10
10
  />
11
- <!-- @vue-expect-error -->
12
11
  <WPopup
13
12
  :model-value="showSubMenu"
14
13
  :preferred-horizontal="['right-most']"
@@ -10,7 +10,7 @@ declare module "@tiptap/core" {
10
10
  embeddedCommandRedirect: {
11
11
  embeddedCommandRedirect: (
12
12
  commandName: string,
13
- args: any,
13
+ args: any
14
14
  ) => ReturnType
15
15
  }
16
16
  }
@@ -84,7 +84,6 @@
84
84
  @pointerdown.prevent
85
85
  @pointerup.prevent="pickBlockId"
86
86
  >
87
- <!-- @vue-expect-error -->
88
87
  <Editor
89
88
  :is-embedded="true"
90
89
  :doc-id="newEmbedId.docId"
@@ -1,5 +1,4 @@
1
1
  <template>
2
- <!-- @vue-expect-error -->
3
2
  <node-view-wrapper
4
3
  :class="`
5
4
  group/embedded-doc
@@ -1,5 +1,4 @@
1
1
  <template>
2
- <!-- @vue-expect-error contenteditable exists -->
3
2
  <node-view-wrapper
4
3
  :class="twMerge(`
5
4
  group/file-loader
@@ -46,7 +46,7 @@ export type IFileLoaderHandler<
46
46
  editor: Editor,
47
47
  pos: number | undefined,
48
48
  error: Error,
49
- loadingKey: TKey,
49
+ loadingKey: TKey
50
50
  ) => void
51
51
 
52
52
  /**
@@ -77,7 +77,7 @@ export type IFileLoaderHandler<
77
77
  editor: Editor,
78
78
  pos: number,
79
79
  res: T,
80
- loadingKey: TKey,
80
+ loadingKey: TKey
81
81
  ) => void
82
82
  /**
83
83
  * Return the file (or whatever type you'd like) to allow the extension to handle it.