File size: 3,044 Bytes
561e6f0
 
 
 
 
 
 
 
76fc93a
561e6f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76fc93a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561e6f0
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { Node, mergeAttributes } from "@tiptap/core";
import { ReactNodeViewRenderer } from "@tiptap/react";
import { StackView } from "../StackView";

declare module "@tiptap/core" {
  interface Commands<ReturnType> {
    stack: {
      insertStack: (columns?: number) => ReturnType;
      setStackColumns: (pos: number, columns: number) => ReturnType;
    };
  }
}

export const StackColumn = Node.create({
  name: "stackColumn",
  group: "stackColumn",
  content: "block+",
  defining: true,
  isolating: true,

  parseHTML() {
    return [{ tag: 'div[data-type="stack-column"]' }];
  },

  renderHTML({ HTMLAttributes }) {
    return ["div", mergeAttributes(HTMLAttributes, { "data-type": "stack-column" }), 0];
  },
});

export const Stack = Node.create({
  name: "stack",
  group: "block",
  content: "stackColumn{2,4}",
  defining: true,
  isolating: true,

  addAttributes() {
    return {
      layout: { default: "2-column" },
      gap: { default: "medium" },
    };
  },

  parseHTML() {
    return [{ tag: 'div[data-component="stack"]' }];
  },

  renderHTML({ HTMLAttributes }) {
    return [
      "div",
      mergeAttributes(HTMLAttributes, { "data-component": "stack" }),
      0,
    ];
  },

  addCommands() {
    return {
      insertStack:
        (columns = 2) =>
        ({ commands }) => {
          const cols = Array.from({ length: Math.min(Math.max(columns, 2), 4) }, () => ({
            type: "stackColumn",
            content: [{ type: "paragraph" }],
          }));
          return commands.insertContent({
            type: "stack",
            attrs: { layout: `${columns}-column` },
            content: cols,
          });
        },

      setStackColumns:
        (pos, columns) =>
        ({ tr, state, dispatch }) => {
          const target = Math.min(Math.max(columns, 2), 4);
          const node = state.doc.nodeAt(pos);
          if (!node || node.type.name !== "stack") return false;

          const current = node.childCount;
          if (current === target) {
            tr.setNodeMarkup(pos, undefined, { ...node.attrs, layout: `${target}-column` });
            dispatch?.(tr);
            return true;
          }

          tr.setNodeMarkup(pos, undefined, { ...node.attrs, layout: `${target}-column` });

          if (target > current) {
            const colType = state.schema.nodes.stackColumn;
            const pType = state.schema.nodes.paragraph;
            const insertPos = pos + node.nodeSize - 1;
            for (let i = 0; i < target - current; i++) {
              tr.insert(insertPos + i, colType.create(null, pType.create()));
            }
          } else {
            let removeFrom = pos + 1;
            for (let i = 0; i < target; i++) {
              removeFrom += node.child(i).nodeSize;
            }
            const removeTo = pos + node.nodeSize - 1;
            tr.delete(removeFrom, removeTo);
          }

          dispatch?.(tr);
          return true;
        },
    };
  },

  addNodeView() {
    return ReactNodeViewRenderer(StackView);
  },
});