@aysys/extension-div 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,16 @@
1
- # Temporary Holding Version
1
+ # ExtensionDiv
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Tiptap に `div`、レスポンシブグリッド、sticky ノードを追加します。
4
+
5
+ ## 使い方
6
+
7
+ ```bash
8
+ import { Div, Grid, Sticky } from "@aysys/extension-div";
9
+
10
+ const extensions = [Div, Grid, Sticky];
11
+ editor.commands.wrapDiv();
12
+ editor.commands.createGrid(3, "1rem", true);
13
+ editor.commands.createSticky("1rem", "top");
14
+ ```
15
+
16
+ `Div` は選択範囲を div で囲む・解除するコマンドを提供します。`Grid` は列数や間隔、レスポンシブ動作を指定して作成・更新できます。`Sticky` は上下いずれかへの固定位置を設定できます。
package/dist/index.cjs ADDED
@@ -0,0 +1,243 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _tiptap_core = require("@tiptap/core");
3
+ let _tiptap_pm_state = require("@tiptap/pm/state");
4
+ //#region src/div.ts
5
+ const Div = _tiptap_core.Node.create({
6
+ name: "div",
7
+ group: "block",
8
+ content: "block*",
9
+ selectable: true,
10
+ isolating: true,
11
+ addOptions() {
12
+ return {
13
+ HTMLAttributes: {},
14
+ style: {}
15
+ };
16
+ },
17
+ parseHTML() {
18
+ return [{ tag: "div" }];
19
+ },
20
+ renderHTML({ HTMLAttributes }) {
21
+ const style = Object.entries(this.options.style).map(([key, value]) => `${key}: ${value}`).join("; ");
22
+ return [
23
+ "div",
24
+ (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}),
25
+ 0
26
+ ];
27
+ },
28
+ addCommands() {
29
+ return {
30
+ toggleDiv: () => ({ editor, chain }) => editor.isActive(this.name) ? chain().unwrapDiv().run() : chain().focus().wrapIn(this.name).run(),
31
+ unwrapDiv: () => ({ chain }) => chain().focus().command(({ tr, dispatch, commands }) => {
32
+ const { $from } = tr.selection;
33
+ const selectedNode = tr.selection instanceof _tiptap_pm_state.NodeSelection ? tr.selection.node : null;
34
+ if (selectedNode?.type.name === this.name) {
35
+ tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);
36
+ commands.setTextSelection($from.pos + 1);
37
+ dispatch?.(tr);
38
+ return true;
39
+ }
40
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
41
+ if ($from.node(depth).type.name !== this.name) continue;
42
+ const position = $from.pos;
43
+ tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);
44
+ commands.setTextSelection(tr.mapping.map(position, -1));
45
+ dispatch?.(tr);
46
+ return true;
47
+ }
48
+ return false;
49
+ }).run(),
50
+ wrapDiv: () => ({ chain }) => {
51
+ return chain().focus().wrapIn(this.name).run();
52
+ }
53
+ };
54
+ }
55
+ });
56
+ //#endregion
57
+ //#region src/utils.ts
58
+ const getStyle = (element, property, attribute) => element.style.getPropertyValue(property) || element.getAttribute(attribute) || "";
59
+ const renderStyleAttribute = (property, value) => {
60
+ if (!value) return;
61
+ return { style: `${property}: ${value}` };
62
+ };
63
+ //#endregion
64
+ //#region src/grid.ts
65
+ const Grid = Div.extend({
66
+ name: "grid",
67
+ addOptions() {
68
+ return {
69
+ ...this.parent?.(),
70
+ maxColumns: 12
71
+ };
72
+ },
73
+ parseHTML() {
74
+ return [{
75
+ tag: `div[data-type="${this.name}"]`,
76
+ priority: 100
77
+ }];
78
+ },
79
+ renderHTML({ HTMLAttributes }) {
80
+ return [
81
+ "div",
82
+ (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes ?? {}, this.options.style ?? {}, HTMLAttributes, { "data-type": this.name }),
83
+ 0
84
+ ];
85
+ },
86
+ addAttributes() {
87
+ return {
88
+ display: {
89
+ default: "grid",
90
+ parseHTML: (element) => getStyle(element, "display", "display"),
91
+ renderHTML: ({ display }) => renderStyleAttribute("display", display)
92
+ },
93
+ cols: {
94
+ default: null,
95
+ parseHTML: (element) => {
96
+ const cols = element.getAttribute("cols");
97
+ return cols === null ? null : Number(cols);
98
+ },
99
+ renderHTML: ({ cols, gap, responsive }) => {
100
+ if (responsive) {
101
+ const availableWidth = ["100%", ...Array.from({ length: Number(cols) - 1 }, () => gap || "0px").map((value) => `- ${value}`)].join(" ");
102
+ return {
103
+ cols,
104
+ ...renderStyleAttribute("grid-template-columns", `repeat(auto-fit, minmax(min(100%, max(200px, calc((${availableWidth}) / ${cols}))), 1fr))`)
105
+ };
106
+ }
107
+ const columns = `repeat(${cols}, 1fr)`;
108
+ return {
109
+ cols,
110
+ ...renderStyleAttribute("grid-template-columns", columns)
111
+ };
112
+ }
113
+ },
114
+ gap: {
115
+ default: "",
116
+ parseHTML: (element) => getStyle(element, "gap", "gap"),
117
+ renderHTML: ({ gap }) => renderStyleAttribute("gap", gap)
118
+ },
119
+ responsive: {
120
+ default: false,
121
+ parseHTML: (element) => element.getAttribute("responsive") === "true",
122
+ renderHTML: ({ responsive }) => {
123
+ if (responsive) return { responsive: true };
124
+ }
125
+ }
126
+ };
127
+ },
128
+ addCommands() {
129
+ return {
130
+ createGrid: (cols, gap, responsive) => ({ chain }) => {
131
+ cols ??= 2;
132
+ if (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols) return false;
133
+ const param = {
134
+ cols,
135
+ gap,
136
+ responsive
137
+ };
138
+ return chain().wrapIn(this.name, param).run();
139
+ },
140
+ updateGrid: (cols, gap, responsive) => ({ chain }) => {
141
+ if (cols !== void 0 && (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols)) return false;
142
+ const param = {};
143
+ if (cols !== void 0) param.cols = cols;
144
+ if (gap !== void 0) param.gap = gap;
145
+ if (responsive !== void 0) param.responsive = responsive;
146
+ return chain().updateAttributes(this.name, param).run();
147
+ }
148
+ };
149
+ }
150
+ });
151
+ //#endregion
152
+ //#region src/sticky.ts
153
+ const STICKY_STYLES = `
154
+ *[data-type="sticky"] {
155
+ z-index: 1;
156
+ }
157
+ `;
158
+ const Sticky = Div.extend({
159
+ name: "sticky",
160
+ addStorage() {
161
+ return { style: void 0 };
162
+ },
163
+ onCreate() {
164
+ if (typeof document === "undefined") return;
165
+ const style = document.createElement("style");
166
+ style.textContent = STICKY_STYLES;
167
+ document.head.appendChild(style);
168
+ this.storage.style = style;
169
+ },
170
+ onDestroy() {
171
+ this.storage.style?.remove();
172
+ },
173
+ parseHTML() {
174
+ return [{
175
+ tag: `div[data-type="${this.name}"]`,
176
+ priority: 100
177
+ }];
178
+ },
179
+ renderHTML({ HTMLAttributes }) {
180
+ const style = Object.entries(this.options.style).map(([key, value]) => `${key}: ${value}`).join("; ");
181
+ return [
182
+ "div",
183
+ (0, _tiptap_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}, { "data-type": this.name }),
184
+ 0
185
+ ];
186
+ },
187
+ addAttributes() {
188
+ return {
189
+ position: {
190
+ default: "sticky",
191
+ parseHTML: (element) => getStyle(element, "position", "position"),
192
+ renderHTML: ({ position }) => renderStyleAttribute("position", position)
193
+ },
194
+ top: {
195
+ default: "",
196
+ parseHTML: (element) => getStyle(element, "top", "top"),
197
+ renderHTML: ({ top }) => renderStyleAttribute("top", top)
198
+ },
199
+ bottom: {
200
+ default: "",
201
+ parseHTML: (element) => getStyle(element, "bottom", "bottom"),
202
+ renderHTML: ({ bottom }) => renderStyleAttribute("bottom", bottom)
203
+ }
204
+ };
205
+ },
206
+ addCommands() {
207
+ return {
208
+ createSticky: (distance, placement) => ({ chain, state }) => {
209
+ const { $from } = state.selection;
210
+ if (Array.from({ length: $from.depth }, (_, index) => index + 1).some((depth) => $from.node(depth).type.name === this.name)) return false;
211
+ const param = {};
212
+ if (placement === "top") param.top = distance || "0";
213
+ else param.bottom = distance || "0";
214
+ return chain().focus().wrapIn(this.name, param).run();
215
+ },
216
+ removeSticky: () => ({ chain }) => chain().focus().command(({ tr, dispatch, commands }) => {
217
+ const { $from } = tr.selection;
218
+ const selectedNode = tr.selection instanceof _tiptap_pm_state.NodeSelection ? tr.selection.node : null;
219
+ if (selectedNode?.type.name === this.name) {
220
+ tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);
221
+ commands.setTextSelection($from.pos + 1);
222
+ dispatch?.(tr);
223
+ return true;
224
+ }
225
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
226
+ if ($from.node(depth).type.name !== this.name) continue;
227
+ const position = $from.pos;
228
+ tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);
229
+ commands.setTextSelection(tr.mapping.map(position, -1));
230
+ dispatch?.(tr);
231
+ return true;
232
+ }
233
+ return false;
234
+ }).run()
235
+ };
236
+ }
237
+ });
238
+ //#endregion
239
+ exports.Div = Div;
240
+ exports.Grid = Grid;
241
+ exports.Sticky = Sticky;
242
+
243
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["Node","mergeAttributes","NodeSelection","mergeAttributes","mergeAttributes","NodeSelection"],"sources":["../src/div.ts","../src/utils.ts","../src/grid.ts","../src/sticky.ts"],"sourcesContent":["import { mergeAttributes, Node } from \"@tiptap/core\";\nimport { NodeSelection } from \"@tiptap/pm/state\";\n\nexport interface DivOptions {\n HTMLAttributes: Record<string, any>;\n style: Record<string, any>;\n}\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n div: {\n /**\n * 選択中のnodeをdivで囲む\n */\n wrapDiv: () => ReturnType;\n /**\n * 選択中のDiv要素を解除する\n */\n unwrapDiv: () => ReturnType;\n /**\n * 選択中のnodeをdiv要素に切り替えする\n */\n toggleDiv: () => ReturnType;\n };\n }\n}\n\nexport const Div = Node.create<DivOptions>({\n name: \"div\",\n group: \"block\",\n content: \"block*\",\n selectable: true,\n\n // ネストしたDivのそれぞれに焦点を当てるのに必要\n isolating: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n style: {},\n };\n },\n\n parseHTML() {\n return [\n {\n tag: \"div\",\n },\n ];\n },\n\n renderHTML({ HTMLAttributes }) {\n const style = Object.entries(this.options.style)\n .map(([key, value]) => `${key}: ${value}`)\n .join(\"; \");\n\n return [\n \"div\",\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}),\n 0,\n ];\n },\n\n addCommands() {\n return {\n toggleDiv:\n () =>\n ({ editor, chain }) =>\n editor.isActive(this.name)\n ? chain().unwrapDiv().run()\n : chain().focus().wrapIn(this.name).run(),\n\n unwrapDiv:\n () =>\n ({ chain }) =>\n chain()\n .focus()\n .command(({ tr, dispatch, commands }) => {\n const { $from } = tr.selection;\n\n // Div自体が選択されている場合は、NodeSelectionの範囲をコンテンツで置き換える\n const selectedNode = tr.selection instanceof NodeSelection ? tr.selection.node : null;\n\n if (selectedNode?.type.name === this.name) {\n tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);\n commands.setTextSelection($from.pos + 1);\n dispatch?.(tr);\n return true;\n }\n\n // カーソルを含む親Divを探し、Divの外側からコンテンツを戻す\n for (let depth = $from.depth; depth > 0; depth -= 1) {\n if ($from.node(depth).type.name !== this.name) continue;\n\n const position = $from.pos;\n tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);\n // ノード置換による位置ずれをトランザクションのマッピングで補正する\n commands.setTextSelection(tr.mapping.map(position, -1));\n dispatch?.(tr);\n return true;\n }\n\n return false;\n })\n .run(),\n\n wrapDiv:\n () =>\n ({ chain }) => {\n return chain().focus().wrapIn(this.name).run();\n },\n };\n },\n});\n","export const getStyle = (element: HTMLElement, property: string, attribute: string) =>\n element.style.getPropertyValue(property) || element.getAttribute(attribute) || \"\";\n\nexport const renderStyleAttribute = (property: string, value: string) => {\n if (!value) return;\n\n return {\n style: `${property}: ${value}`,\n };\n};\n","import { mergeAttributes } from \"@tiptap/core\";\nimport { Div, type DivOptions } from \"./div\";\nimport { getStyle, renderStyleAttribute } from \"./utils\";\n\ntype GridOptions = Partial<DivOptions> & {\n maxColumns: number;\n};\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n grid: {\n createGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;\n updateGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;\n };\n }\n}\n\nexport const Grid = Div.extend<GridOptions>({\n name: \"grid\",\n addOptions() {\n return {\n ...this.parent?.(),\n maxColumns: 12,\n };\n },\n\n parseHTML() {\n return [{ tag: `div[data-type=\"${this.name}\"]`, priority: 100 }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n \"div\",\n mergeAttributes(this.options.HTMLAttributes ?? {}, this.options.style ?? {}, HTMLAttributes, {\n \"data-type\": this.name,\n }),\n 0,\n ];\n },\n addAttributes() {\n return {\n display: {\n default: \"grid\",\n parseHTML: (element) => getStyle(element, \"display\", \"display\"),\n renderHTML: ({ display }) => renderStyleAttribute(\"display\", display),\n },\n cols: {\n default: null,\n parseHTML: (element) => {\n const cols = element.getAttribute(\"cols\");\n return cols === null ? null : Number(cols);\n },\n renderHTML: ({ cols, gap, responsive }) => {\n if (responsive) {\n const gaps = Array.from({ length: Number(cols) - 1 }, () => gap || \"0px\");\n const availableWidth = [\"100%\", ...gaps.map((value) => `- ${value}`)].join(\" \");\n return {\n cols: cols,\n ...renderStyleAttribute(\n \"grid-template-columns\",\n `repeat(auto-fit, minmax(min(100%, max(200px, calc((${availableWidth}) / ${cols}))), 1fr))`,\n ),\n };\n }\n const columns = `repeat(${cols}, 1fr)`;\n return {\n cols: cols,\n ...renderStyleAttribute(\"grid-template-columns\", columns),\n };\n },\n },\n gap: {\n default: \"\",\n parseHTML: (element) => getStyle(element, \"gap\", \"gap\"),\n renderHTML: ({ gap }) => renderStyleAttribute(\"gap\", gap),\n },\n responsive: {\n default: false,\n parseHTML: (element) => element.getAttribute(\"responsive\") === \"true\",\n renderHTML: ({ responsive }) => {\n if (responsive) return { responsive: true };\n },\n },\n };\n },\n\n addCommands() {\n return {\n createGrid:\n (cols, gap, responsive) =>\n ({ chain }) => {\n cols ??= 2;\n if (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols) return false;\n const param = {\n cols,\n gap: gap,\n responsive,\n };\n return chain().wrapIn(this.name, param).run();\n },\n updateGrid:\n (cols, gap, responsive) =>\n ({ chain }) => {\n if (\n cols !== undefined &&\n (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols)\n ) {\n return false;\n }\n\n const param: Record<string, unknown> = {};\n\n if (cols !== undefined) param.cols = cols;\n if (gap !== undefined) param.gap = gap;\n if (responsive !== undefined) param.responsive = responsive;\n\n return chain().updateAttributes(this.name, param).run();\n },\n };\n },\n});\n","import { mergeAttributes } from \"@tiptap/core\";\nimport { NodeSelection } from \"@tiptap/pm/state\";\nimport { Div } from \"./div\";\nimport { getStyle, renderStyleAttribute } from \"./utils\";\n\ntype StickyOptions = {\n HTMLAttributes: Record<string, any>;\n style: Record<string, any>;\n};\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n sticky: {\n createSticky: (distance?: string, placement?: \"top\" | \"bottom\") => ReturnType;\n removeSticky: () => ReturnType;\n };\n }\n}\n\nconst STICKY_STYLES = `\n*[data-type=\"sticky\"] {\n z-index: 1;\n}\n`;\n\nexport const Sticky = Div.extend<StickyOptions>({\n name: \"sticky\",\n\n addStorage() {\n return { style: undefined as HTMLStyleElement | undefined };\n },\n onCreate() {\n if (typeof document === \"undefined\") return;\n\n const style = document.createElement(\"style\");\n style.textContent = STICKY_STYLES;\n document.head.appendChild(style);\n this.storage.style = style;\n },\n onDestroy() {\n this.storage.style?.remove();\n },\n\n parseHTML() {\n return [{ tag: `div[data-type=\"${this.name}\"]`, priority: 100 }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const style = Object.entries(this.options.style)\n .map(([key, value]) => `${key}: ${value}`)\n .join(\"; \");\n\n return [\n \"div\",\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}, {\n \"data-type\": this.name,\n }),\n 0,\n ];\n },\n\n addAttributes() {\n return {\n position: {\n default: \"sticky\",\n parseHTML: (element) => getStyle(element, \"position\", \"position\"),\n renderHTML: ({ position }) => renderStyleAttribute(\"position\", position),\n },\n top: {\n default: \"\",\n parseHTML: (element) => getStyle(element, \"top\", \"top\"),\n renderHTML: ({ top }) => renderStyleAttribute(\"top\", top),\n },\n bottom: {\n default: \"\",\n parseHTML: (element) => getStyle(element, \"bottom\", \"bottom\"),\n renderHTML: ({ bottom }) => renderStyleAttribute(\"bottom\", bottom),\n },\n };\n },\n\n // コマンドの追加\n addCommands() {\n return {\n createSticky:\n (distance, placement) =>\n ({ chain, state }) => {\n const { $from } = state.selection;\n const isInsideSticky = Array.from({ length: $from.depth }, (_, index) => index + 1).some(\n (depth) => $from.node(depth).type.name === this.name,\n );\n\n if (isInsideSticky) return false;\n\n const param: Record<string, unknown> = {};\n\n if (placement === \"top\") param.top = distance || \"0\";\n else param.bottom = distance || \"0\";\n\n return chain().focus().wrapIn(this.name, param).run();\n },\n\n removeSticky:\n () =>\n ({ chain }) =>\n chain()\n .focus()\n .command(({ tr, dispatch, commands }) => {\n const { $from } = tr.selection;\n const selectedNode = tr.selection instanceof NodeSelection ? tr.selection.node : null;\n\n if (selectedNode?.type.name === this.name) {\n tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);\n commands.setTextSelection($from.pos + 1);\n dispatch?.(tr);\n return true;\n }\n\n for (let depth = $from.depth; depth > 0; depth -= 1) {\n if ($from.node(depth).type.name !== this.name) continue;\n\n const position = $from.pos;\n tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);\n commands.setTextSelection(tr.mapping.map(position, -1));\n dispatch?.(tr);\n return true;\n }\n\n return false;\n })\n .run(),\n };\n },\n});\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { Node } from "@tiptap/core";
2
+ //#region src/div.d.ts
3
+ export interface DivOptions {
4
+ HTMLAttributes: Record<string, any>;
5
+ style: Record<string, any>;
6
+ }
7
+ declare module "@tiptap/core" {
8
+ interface Commands<ReturnType> {
9
+ div: {
10
+ /**
11
+ * 選択中のnodeをdivで囲む
12
+ */
13
+ wrapDiv: () => ReturnType;
14
+ /**
15
+ * 選択中のDiv要素を解除する
16
+ */
17
+ unwrapDiv: () => ReturnType;
18
+ /**
19
+ * 選択中のnodeをdiv要素に切り替えする
20
+ */
21
+ toggleDiv: () => ReturnType;
22
+ };
23
+ }
24
+ }
25
+ export declare const Div: Node<DivOptions, any>;
26
+ //#endregion
27
+ //#region src/grid.d.ts
28
+ type GridOptions = Partial<DivOptions> & {
29
+ maxColumns: number;
30
+ };
31
+ declare module "@tiptap/core" {
32
+ interface Commands<ReturnType> {
33
+ grid: {
34
+ createGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;
35
+ updateGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;
36
+ };
37
+ }
38
+ }
39
+ export declare const Grid: import("@tiptap/core").Node<GridOptions, any>;
40
+ //#endregion
41
+ //#region src/sticky.d.ts
42
+ type StickyOptions = {
43
+ HTMLAttributes: Record<string, any>;
44
+ style: Record<string, any>;
45
+ };
46
+ declare module "@tiptap/core" {
47
+ interface Commands<ReturnType> {
48
+ sticky: {
49
+ createSticky: (distance?: string, placement?: "top" | "bottom") => ReturnType;
50
+ removeSticky: () => ReturnType;
51
+ };
52
+ }
53
+ }
54
+ export declare const Sticky: import("@tiptap/core").Node<StickyOptions, any>;
55
+ //#endregion
56
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,56 @@
1
+ import { Node } from "@tiptap/core";
2
+ //#region src/div.d.ts
3
+ export interface DivOptions {
4
+ HTMLAttributes: Record<string, any>;
5
+ style: Record<string, any>;
6
+ }
7
+ declare module "@tiptap/core" {
8
+ interface Commands<ReturnType> {
9
+ div: {
10
+ /**
11
+ * 選択中のnodeをdivで囲む
12
+ */
13
+ wrapDiv: () => ReturnType;
14
+ /**
15
+ * 選択中のDiv要素を解除する
16
+ */
17
+ unwrapDiv: () => ReturnType;
18
+ /**
19
+ * 選択中のnodeをdiv要素に切り替えする
20
+ */
21
+ toggleDiv: () => ReturnType;
22
+ };
23
+ }
24
+ }
25
+ export declare const Div: Node<DivOptions, any>;
26
+ //#endregion
27
+ //#region src/grid.d.ts
28
+ type GridOptions = Partial<DivOptions> & {
29
+ maxColumns: number;
30
+ };
31
+ declare module "@tiptap/core" {
32
+ interface Commands<ReturnType> {
33
+ grid: {
34
+ createGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;
35
+ updateGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;
36
+ };
37
+ }
38
+ }
39
+ export declare const Grid: import("@tiptap/core").Node<GridOptions, any>;
40
+ //#endregion
41
+ //#region src/sticky.d.ts
42
+ type StickyOptions = {
43
+ HTMLAttributes: Record<string, any>;
44
+ style: Record<string, any>;
45
+ };
46
+ declare module "@tiptap/core" {
47
+ interface Commands<ReturnType> {
48
+ sticky: {
49
+ createSticky: (distance?: string, placement?: "top" | "bottom") => ReturnType;
50
+ removeSticky: () => ReturnType;
51
+ };
52
+ }
53
+ }
54
+ export declare const Sticky: import("@tiptap/core").Node<StickyOptions, any>;
55
+ //#endregion
56
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,240 @@
1
+ import { Node, mergeAttributes } from "@tiptap/core";
2
+ import { NodeSelection } from "@tiptap/pm/state";
3
+ //#region src/div.ts
4
+ const Div = Node.create({
5
+ name: "div",
6
+ group: "block",
7
+ content: "block*",
8
+ selectable: true,
9
+ isolating: true,
10
+ addOptions() {
11
+ return {
12
+ HTMLAttributes: {},
13
+ style: {}
14
+ };
15
+ },
16
+ parseHTML() {
17
+ return [{ tag: "div" }];
18
+ },
19
+ renderHTML({ HTMLAttributes }) {
20
+ const style = Object.entries(this.options.style).map(([key, value]) => `${key}: ${value}`).join("; ");
21
+ return [
22
+ "div",
23
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}),
24
+ 0
25
+ ];
26
+ },
27
+ addCommands() {
28
+ return {
29
+ toggleDiv: () => ({ editor, chain }) => editor.isActive(this.name) ? chain().unwrapDiv().run() : chain().focus().wrapIn(this.name).run(),
30
+ unwrapDiv: () => ({ chain }) => chain().focus().command(({ tr, dispatch, commands }) => {
31
+ const { $from } = tr.selection;
32
+ const selectedNode = tr.selection instanceof NodeSelection ? tr.selection.node : null;
33
+ if (selectedNode?.type.name === this.name) {
34
+ tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);
35
+ commands.setTextSelection($from.pos + 1);
36
+ dispatch?.(tr);
37
+ return true;
38
+ }
39
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
40
+ if ($from.node(depth).type.name !== this.name) continue;
41
+ const position = $from.pos;
42
+ tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);
43
+ commands.setTextSelection(tr.mapping.map(position, -1));
44
+ dispatch?.(tr);
45
+ return true;
46
+ }
47
+ return false;
48
+ }).run(),
49
+ wrapDiv: () => ({ chain }) => {
50
+ return chain().focus().wrapIn(this.name).run();
51
+ }
52
+ };
53
+ }
54
+ });
55
+ //#endregion
56
+ //#region src/utils.ts
57
+ const getStyle = (element, property, attribute) => element.style.getPropertyValue(property) || element.getAttribute(attribute) || "";
58
+ const renderStyleAttribute = (property, value) => {
59
+ if (!value) return;
60
+ return { style: `${property}: ${value}` };
61
+ };
62
+ //#endregion
63
+ //#region src/grid.ts
64
+ const Grid = Div.extend({
65
+ name: "grid",
66
+ addOptions() {
67
+ return {
68
+ ...this.parent?.(),
69
+ maxColumns: 12
70
+ };
71
+ },
72
+ parseHTML() {
73
+ return [{
74
+ tag: `div[data-type="${this.name}"]`,
75
+ priority: 100
76
+ }];
77
+ },
78
+ renderHTML({ HTMLAttributes }) {
79
+ return [
80
+ "div",
81
+ mergeAttributes(this.options.HTMLAttributes ?? {}, this.options.style ?? {}, HTMLAttributes, { "data-type": this.name }),
82
+ 0
83
+ ];
84
+ },
85
+ addAttributes() {
86
+ return {
87
+ display: {
88
+ default: "grid",
89
+ parseHTML: (element) => getStyle(element, "display", "display"),
90
+ renderHTML: ({ display }) => renderStyleAttribute("display", display)
91
+ },
92
+ cols: {
93
+ default: null,
94
+ parseHTML: (element) => {
95
+ const cols = element.getAttribute("cols");
96
+ return cols === null ? null : Number(cols);
97
+ },
98
+ renderHTML: ({ cols, gap, responsive }) => {
99
+ if (responsive) {
100
+ const availableWidth = ["100%", ...Array.from({ length: Number(cols) - 1 }, () => gap || "0px").map((value) => `- ${value}`)].join(" ");
101
+ return {
102
+ cols,
103
+ ...renderStyleAttribute("grid-template-columns", `repeat(auto-fit, minmax(min(100%, max(200px, calc((${availableWidth}) / ${cols}))), 1fr))`)
104
+ };
105
+ }
106
+ const columns = `repeat(${cols}, 1fr)`;
107
+ return {
108
+ cols,
109
+ ...renderStyleAttribute("grid-template-columns", columns)
110
+ };
111
+ }
112
+ },
113
+ gap: {
114
+ default: "",
115
+ parseHTML: (element) => getStyle(element, "gap", "gap"),
116
+ renderHTML: ({ gap }) => renderStyleAttribute("gap", gap)
117
+ },
118
+ responsive: {
119
+ default: false,
120
+ parseHTML: (element) => element.getAttribute("responsive") === "true",
121
+ renderHTML: ({ responsive }) => {
122
+ if (responsive) return { responsive: true };
123
+ }
124
+ }
125
+ };
126
+ },
127
+ addCommands() {
128
+ return {
129
+ createGrid: (cols, gap, responsive) => ({ chain }) => {
130
+ cols ??= 2;
131
+ if (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols) return false;
132
+ const param = {
133
+ cols,
134
+ gap,
135
+ responsive
136
+ };
137
+ return chain().wrapIn(this.name, param).run();
138
+ },
139
+ updateGrid: (cols, gap, responsive) => ({ chain }) => {
140
+ if (cols !== void 0 && (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols)) return false;
141
+ const param = {};
142
+ if (cols !== void 0) param.cols = cols;
143
+ if (gap !== void 0) param.gap = gap;
144
+ if (responsive !== void 0) param.responsive = responsive;
145
+ return chain().updateAttributes(this.name, param).run();
146
+ }
147
+ };
148
+ }
149
+ });
150
+ //#endregion
151
+ //#region src/sticky.ts
152
+ const STICKY_STYLES = `
153
+ *[data-type="sticky"] {
154
+ z-index: 1;
155
+ }
156
+ `;
157
+ const Sticky = Div.extend({
158
+ name: "sticky",
159
+ addStorage() {
160
+ return { style: void 0 };
161
+ },
162
+ onCreate() {
163
+ if (typeof document === "undefined") return;
164
+ const style = document.createElement("style");
165
+ style.textContent = STICKY_STYLES;
166
+ document.head.appendChild(style);
167
+ this.storage.style = style;
168
+ },
169
+ onDestroy() {
170
+ this.storage.style?.remove();
171
+ },
172
+ parseHTML() {
173
+ return [{
174
+ tag: `div[data-type="${this.name}"]`,
175
+ priority: 100
176
+ }];
177
+ },
178
+ renderHTML({ HTMLAttributes }) {
179
+ const style = Object.entries(this.options.style).map(([key, value]) => `${key}: ${value}`).join("; ");
180
+ return [
181
+ "div",
182
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}, { "data-type": this.name }),
183
+ 0
184
+ ];
185
+ },
186
+ addAttributes() {
187
+ return {
188
+ position: {
189
+ default: "sticky",
190
+ parseHTML: (element) => getStyle(element, "position", "position"),
191
+ renderHTML: ({ position }) => renderStyleAttribute("position", position)
192
+ },
193
+ top: {
194
+ default: "",
195
+ parseHTML: (element) => getStyle(element, "top", "top"),
196
+ renderHTML: ({ top }) => renderStyleAttribute("top", top)
197
+ },
198
+ bottom: {
199
+ default: "",
200
+ parseHTML: (element) => getStyle(element, "bottom", "bottom"),
201
+ renderHTML: ({ bottom }) => renderStyleAttribute("bottom", bottom)
202
+ }
203
+ };
204
+ },
205
+ addCommands() {
206
+ return {
207
+ createSticky: (distance, placement) => ({ chain, state }) => {
208
+ const { $from } = state.selection;
209
+ if (Array.from({ length: $from.depth }, (_, index) => index + 1).some((depth) => $from.node(depth).type.name === this.name)) return false;
210
+ const param = {};
211
+ if (placement === "top") param.top = distance || "0";
212
+ else param.bottom = distance || "0";
213
+ return chain().focus().wrapIn(this.name, param).run();
214
+ },
215
+ removeSticky: () => ({ chain }) => chain().focus().command(({ tr, dispatch, commands }) => {
216
+ const { $from } = tr.selection;
217
+ const selectedNode = tr.selection instanceof NodeSelection ? tr.selection.node : null;
218
+ if (selectedNode?.type.name === this.name) {
219
+ tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);
220
+ commands.setTextSelection($from.pos + 1);
221
+ dispatch?.(tr);
222
+ return true;
223
+ }
224
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
225
+ if ($from.node(depth).type.name !== this.name) continue;
226
+ const position = $from.pos;
227
+ tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);
228
+ commands.setTextSelection(tr.mapping.map(position, -1));
229
+ dispatch?.(tr);
230
+ return true;
231
+ }
232
+ return false;
233
+ }).run()
234
+ };
235
+ }
236
+ });
237
+ //#endregion
238
+ export { Div, Grid, Sticky };
239
+
240
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/div.ts","../src/utils.ts","../src/grid.ts","../src/sticky.ts"],"sourcesContent":["import { mergeAttributes, Node } from \"@tiptap/core\";\nimport { NodeSelection } from \"@tiptap/pm/state\";\n\nexport interface DivOptions {\n HTMLAttributes: Record<string, any>;\n style: Record<string, any>;\n}\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n div: {\n /**\n * 選択中のnodeをdivで囲む\n */\n wrapDiv: () => ReturnType;\n /**\n * 選択中のDiv要素を解除する\n */\n unwrapDiv: () => ReturnType;\n /**\n * 選択中のnodeをdiv要素に切り替えする\n */\n toggleDiv: () => ReturnType;\n };\n }\n}\n\nexport const Div = Node.create<DivOptions>({\n name: \"div\",\n group: \"block\",\n content: \"block*\",\n selectable: true,\n\n // ネストしたDivのそれぞれに焦点を当てるのに必要\n isolating: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n style: {},\n };\n },\n\n parseHTML() {\n return [\n {\n tag: \"div\",\n },\n ];\n },\n\n renderHTML({ HTMLAttributes }) {\n const style = Object.entries(this.options.style)\n .map(([key, value]) => `${key}: ${value}`)\n .join(\"; \");\n\n return [\n \"div\",\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}),\n 0,\n ];\n },\n\n addCommands() {\n return {\n toggleDiv:\n () =>\n ({ editor, chain }) =>\n editor.isActive(this.name)\n ? chain().unwrapDiv().run()\n : chain().focus().wrapIn(this.name).run(),\n\n unwrapDiv:\n () =>\n ({ chain }) =>\n chain()\n .focus()\n .command(({ tr, dispatch, commands }) => {\n const { $from } = tr.selection;\n\n // Div自体が選択されている場合は、NodeSelectionの範囲をコンテンツで置き換える\n const selectedNode = tr.selection instanceof NodeSelection ? tr.selection.node : null;\n\n if (selectedNode?.type.name === this.name) {\n tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);\n commands.setTextSelection($from.pos + 1);\n dispatch?.(tr);\n return true;\n }\n\n // カーソルを含む親Divを探し、Divの外側からコンテンツを戻す\n for (let depth = $from.depth; depth > 0; depth -= 1) {\n if ($from.node(depth).type.name !== this.name) continue;\n\n const position = $from.pos;\n tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);\n // ノード置換による位置ずれをトランザクションのマッピングで補正する\n commands.setTextSelection(tr.mapping.map(position, -1));\n dispatch?.(tr);\n return true;\n }\n\n return false;\n })\n .run(),\n\n wrapDiv:\n () =>\n ({ chain }) => {\n return chain().focus().wrapIn(this.name).run();\n },\n };\n },\n});\n","export const getStyle = (element: HTMLElement, property: string, attribute: string) =>\n element.style.getPropertyValue(property) || element.getAttribute(attribute) || \"\";\n\nexport const renderStyleAttribute = (property: string, value: string) => {\n if (!value) return;\n\n return {\n style: `${property}: ${value}`,\n };\n};\n","import { mergeAttributes } from \"@tiptap/core\";\nimport { Div, type DivOptions } from \"./div\";\nimport { getStyle, renderStyleAttribute } from \"./utils\";\n\ntype GridOptions = Partial<DivOptions> & {\n maxColumns: number;\n};\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n grid: {\n createGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;\n updateGrid: (cols?: number, gap?: string, responsive?: boolean) => ReturnType;\n };\n }\n}\n\nexport const Grid = Div.extend<GridOptions>({\n name: \"grid\",\n addOptions() {\n return {\n ...this.parent?.(),\n maxColumns: 12,\n };\n },\n\n parseHTML() {\n return [{ tag: `div[data-type=\"${this.name}\"]`, priority: 100 }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n \"div\",\n mergeAttributes(this.options.HTMLAttributes ?? {}, this.options.style ?? {}, HTMLAttributes, {\n \"data-type\": this.name,\n }),\n 0,\n ];\n },\n addAttributes() {\n return {\n display: {\n default: \"grid\",\n parseHTML: (element) => getStyle(element, \"display\", \"display\"),\n renderHTML: ({ display }) => renderStyleAttribute(\"display\", display),\n },\n cols: {\n default: null,\n parseHTML: (element) => {\n const cols = element.getAttribute(\"cols\");\n return cols === null ? null : Number(cols);\n },\n renderHTML: ({ cols, gap, responsive }) => {\n if (responsive) {\n const gaps = Array.from({ length: Number(cols) - 1 }, () => gap || \"0px\");\n const availableWidth = [\"100%\", ...gaps.map((value) => `- ${value}`)].join(\" \");\n return {\n cols: cols,\n ...renderStyleAttribute(\n \"grid-template-columns\",\n `repeat(auto-fit, minmax(min(100%, max(200px, calc((${availableWidth}) / ${cols}))), 1fr))`,\n ),\n };\n }\n const columns = `repeat(${cols}, 1fr)`;\n return {\n cols: cols,\n ...renderStyleAttribute(\"grid-template-columns\", columns),\n };\n },\n },\n gap: {\n default: \"\",\n parseHTML: (element) => getStyle(element, \"gap\", \"gap\"),\n renderHTML: ({ gap }) => renderStyleAttribute(\"gap\", gap),\n },\n responsive: {\n default: false,\n parseHTML: (element) => element.getAttribute(\"responsive\") === \"true\",\n renderHTML: ({ responsive }) => {\n if (responsive) return { responsive: true };\n },\n },\n };\n },\n\n addCommands() {\n return {\n createGrid:\n (cols, gap, responsive) =>\n ({ chain }) => {\n cols ??= 2;\n if (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols) return false;\n const param = {\n cols,\n gap: gap,\n responsive,\n };\n return chain().wrapIn(this.name, param).run();\n },\n updateGrid:\n (cols, gap, responsive) =>\n ({ chain }) => {\n if (\n cols !== undefined &&\n (!Number.isInteger(cols) || cols < 1 || this.options.maxColumns < cols)\n ) {\n return false;\n }\n\n const param: Record<string, unknown> = {};\n\n if (cols !== undefined) param.cols = cols;\n if (gap !== undefined) param.gap = gap;\n if (responsive !== undefined) param.responsive = responsive;\n\n return chain().updateAttributes(this.name, param).run();\n },\n };\n },\n});\n","import { mergeAttributes } from \"@tiptap/core\";\nimport { NodeSelection } from \"@tiptap/pm/state\";\nimport { Div } from \"./div\";\nimport { getStyle, renderStyleAttribute } from \"./utils\";\n\ntype StickyOptions = {\n HTMLAttributes: Record<string, any>;\n style: Record<string, any>;\n};\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n sticky: {\n createSticky: (distance?: string, placement?: \"top\" | \"bottom\") => ReturnType;\n removeSticky: () => ReturnType;\n };\n }\n}\n\nconst STICKY_STYLES = `\n*[data-type=\"sticky\"] {\n z-index: 1;\n}\n`;\n\nexport const Sticky = Div.extend<StickyOptions>({\n name: \"sticky\",\n\n addStorage() {\n return { style: undefined as HTMLStyleElement | undefined };\n },\n onCreate() {\n if (typeof document === \"undefined\") return;\n\n const style = document.createElement(\"style\");\n style.textContent = STICKY_STYLES;\n document.head.appendChild(style);\n this.storage.style = style;\n },\n onDestroy() {\n this.storage.style?.remove();\n },\n\n parseHTML() {\n return [{ tag: `div[data-type=\"${this.name}\"]`, priority: 100 }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const style = Object.entries(this.options.style)\n .map(([key, value]) => `${key}: ${value}`)\n .join(\"; \");\n\n return [\n \"div\",\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, style ? { style } : {}, {\n \"data-type\": this.name,\n }),\n 0,\n ];\n },\n\n addAttributes() {\n return {\n position: {\n default: \"sticky\",\n parseHTML: (element) => getStyle(element, \"position\", \"position\"),\n renderHTML: ({ position }) => renderStyleAttribute(\"position\", position),\n },\n top: {\n default: \"\",\n parseHTML: (element) => getStyle(element, \"top\", \"top\"),\n renderHTML: ({ top }) => renderStyleAttribute(\"top\", top),\n },\n bottom: {\n default: \"\",\n parseHTML: (element) => getStyle(element, \"bottom\", \"bottom\"),\n renderHTML: ({ bottom }) => renderStyleAttribute(\"bottom\", bottom),\n },\n };\n },\n\n // コマンドの追加\n addCommands() {\n return {\n createSticky:\n (distance, placement) =>\n ({ chain, state }) => {\n const { $from } = state.selection;\n const isInsideSticky = Array.from({ length: $from.depth }, (_, index) => index + 1).some(\n (depth) => $from.node(depth).type.name === this.name,\n );\n\n if (isInsideSticky) return false;\n\n const param: Record<string, unknown> = {};\n\n if (placement === \"top\") param.top = distance || \"0\";\n else param.bottom = distance || \"0\";\n\n return chain().focus().wrapIn(this.name, param).run();\n },\n\n removeSticky:\n () =>\n ({ chain }) =>\n chain()\n .focus()\n .command(({ tr, dispatch, commands }) => {\n const { $from } = tr.selection;\n const selectedNode = tr.selection instanceof NodeSelection ? tr.selection.node : null;\n\n if (selectedNode?.type.name === this.name) {\n tr.replaceWith($from.pos, $from.pos + selectedNode.nodeSize, selectedNode.content);\n commands.setTextSelection($from.pos + 1);\n dispatch?.(tr);\n return true;\n }\n\n for (let depth = $from.depth; depth > 0; depth -= 1) {\n if ($from.node(depth).type.name !== this.name) continue;\n\n const position = $from.pos;\n tr.replaceWith($from.before(depth), $from.after(depth), $from.node(depth).content);\n commands.setTextSelection(tr.mapping.map(position, -1));\n dispatch?.(tr);\n return true;\n }\n\n return false;\n })\n .run(),\n };\n },\n});\n"],"mappings":";;;AA2BA,MAAa,MAAM,KAAK,OAAmB;CACzC,MAAM;CACN,OAAO;CACP,SAAS;CACT,YAAY;CAGZ,WAAW;CAEX,aAAa;EACX,OAAO;GACL,gBAAgB,CAAC;GACjB,OAAO,CAAC;EACV;CACF;CAEA,YAAY;EACV,OAAO,CACL,EACE,KAAK,MACP,CACF;CACF;CAEA,WAAW,EAAE,kBAAkB;EAC7B,MAAM,QAAQ,OAAO,QAAQ,KAAK,QAAQ,KAAK,CAAC,CAC7C,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,IAAI,OAAO,CAAC,CACzC,KAAK,IAAI;EAEZ,OAAO;GACL;GACA,gBAAgB,KAAK,QAAQ,gBAAgB,gBAAgB,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC;GACnF;EACF;CACF;CAEA,cAAc;EACZ,OAAO;GACL,kBAEG,EAAE,QAAQ,YACT,OAAO,SAAS,KAAK,IAAI,IACrB,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,IACxB,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,IAAI;GAE9C,kBAEG,EAAE,YACD,MAAM,CAAC,CACJ,MAAM,CAAC,CACP,SAAS,EAAE,IAAI,UAAU,eAAe;IACvC,MAAM,EAAE,UAAU,GAAG;IAGrB,MAAM,eAAe,GAAG,qBAAqB,gBAAgB,GAAG,UAAU,OAAO;IAEjF,IAAI,cAAc,KAAK,SAAS,KAAK,MAAM;KACzC,GAAG,YAAY,MAAM,KAAK,MAAM,MAAM,aAAa,UAAU,aAAa,OAAO;KACjF,SAAS,iBAAiB,MAAM,MAAM,CAAC;KACvC,WAAW,EAAE;KACb,OAAO;IACT;IAGA,KAAK,IAAI,QAAQ,MAAM,OAAO,QAAQ,GAAG,SAAS,GAAG;KACnD,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,KAAK,SAAS,KAAK,MAAM;KAE/C,MAAM,WAAW,MAAM;KACvB,GAAG,YAAY,MAAM,OAAO,KAAK,GAAG,MAAM,MAAM,KAAK,GAAG,MAAM,KAAK,KAAK,CAAC,CAAC,OAAO;KAEjF,SAAS,iBAAiB,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAC;KACtD,WAAW,EAAE;KACb,OAAO;IACT;IAEA,OAAO;GACT,CAAC,CAAC,CACD,IAAI;GAEX,gBAEG,EAAE,YAAY;IACb,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,IAAI;GAC/C;EACJ;CACF;AACF,CAAC;;;ACjHD,MAAa,YAAY,SAAsB,UAAkB,cAC/D,QAAQ,MAAM,iBAAiB,QAAQ,KAAK,QAAQ,aAAa,SAAS,KAAK;AAEjF,MAAa,wBAAwB,UAAkB,UAAkB;CACvE,IAAI,CAAC,OAAO;CAEZ,OAAO,EACL,OAAO,GAAG,SAAS,IAAI,QACzB;AACF;;;ACQA,MAAa,OAAO,IAAI,OAAoB;CAC1C,MAAM;CACN,aAAa;EACX,OAAO;GACL,GAAG,KAAK,SAAS;GACjB,YAAY;EACd;CACF;CAEA,YAAY;EACV,OAAO,CAAC;GAAE,KAAK,kBAAkB,KAAK,KAAK;GAAK,UAAU;EAAI,CAAC;CACjE;CAEA,WAAW,EAAE,kBAAkB;EAC7B,OAAO;GACL;GACA,gBAAgB,KAAK,QAAQ,kBAAkB,CAAC,GAAG,KAAK,QAAQ,SAAS,CAAC,GAAG,gBAAgB,EAC3F,aAAa,KAAK,KACpB,CAAC;GACD;EACF;CACF;CACA,gBAAgB;EACd,OAAO;GACL,SAAS;IACP,SAAS;IACT,YAAY,YAAY,SAAS,SAAS,WAAW,SAAS;IAC9D,aAAa,EAAE,cAAc,qBAAqB,WAAW,OAAO;GACtE;GACA,MAAM;IACJ,SAAS;IACT,YAAY,YAAY;KACtB,MAAM,OAAO,QAAQ,aAAa,MAAM;KACxC,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI;IAC3C;IACA,aAAa,EAAE,MAAM,KAAK,iBAAiB;KACzC,IAAI,YAAY;MAEd,MAAM,iBAAiB,CAAC,QAAQ,GADnB,MAAM,KAAK,EAAE,QAAQ,OAAO,IAAI,IAAI,EAAE,SAAS,OAAO,KAC7B,CAAC,CAAC,KAAK,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG;MAC9E,OAAO;OACC;OACN,GAAG,qBACD,yBACA,sDAAsD,eAAe,MAAM,KAAK,WAClF;MACF;KACF;KACA,MAAM,UAAU,UAAU,KAAK;KAC/B,OAAO;MACC;MACN,GAAG,qBAAqB,yBAAyB,OAAO;KAC1D;IACF;GACF;GACA,KAAK;IACH,SAAS;IACT,YAAY,YAAY,SAAS,SAAS,OAAO,KAAK;IACtD,aAAa,EAAE,UAAU,qBAAqB,OAAO,GAAG;GAC1D;GACA,YAAY;IACV,SAAS;IACT,YAAY,YAAY,QAAQ,aAAa,YAAY,MAAM;IAC/D,aAAa,EAAE,iBAAiB;KAC9B,IAAI,YAAY,OAAO,EAAE,YAAY,KAAK;IAC5C;GACF;EACF;CACF;CAEA,cAAc;EACZ,OAAO;GACL,aACG,MAAM,KAAK,gBACX,EAAE,YAAY;IACb,SAAS;IACT,IAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,KAAK,QAAQ,aAAa,MAAM,OAAO;IAClF,MAAM,QAAQ;KACZ;KACK;KACL;IACF;IACA,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,IAAI;GAC9C;GACF,aACG,MAAM,KAAK,gBACX,EAAE,YAAY;IACb,IACE,SAAS,KAAA,MACR,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,KAAK,QAAQ,aAAa,OAElE,OAAO;IAGT,MAAM,QAAiC,CAAC;IAExC,IAAI,SAAS,KAAA,GAAW,MAAM,OAAO;IACrC,IAAI,QAAQ,KAAA,GAAW,MAAM,MAAM;IACnC,IAAI,eAAe,KAAA,GAAW,MAAM,aAAa;IAEjD,OAAO,MAAM,CAAC,CAAC,iBAAiB,KAAK,MAAM,KAAK,CAAC,CAAC,IAAI;GACxD;EACJ;CACF;AACF,CAAC;;;ACrGD,MAAM,gBAAgB;;;;;AAMtB,MAAa,SAAS,IAAI,OAAsB;CAC9C,MAAM;CAEN,aAAa;EACX,OAAO,EAAE,OAAO,KAAA,EAA0C;CAC5D;CACA,WAAW;EACT,IAAI,OAAO,aAAa,aAAa;EAErC,MAAM,QAAQ,SAAS,cAAc,OAAO;EAC5C,MAAM,cAAc;EACpB,SAAS,KAAK,YAAY,KAAK;EAC/B,KAAK,QAAQ,QAAQ;CACvB;CACA,YAAY;EACV,KAAK,QAAQ,OAAO,OAAO;CAC7B;CAEA,YAAY;EACV,OAAO,CAAC;GAAE,KAAK,kBAAkB,KAAK,KAAK;GAAK,UAAU;EAAI,CAAC;CACjE;CAEA,WAAW,EAAE,kBAAkB;EAC7B,MAAM,QAAQ,OAAO,QAAQ,KAAK,QAAQ,KAAK,CAAC,CAC7C,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,IAAI,OAAO,CAAC,CACzC,KAAK,IAAI;EAEZ,OAAO;GACL;GACA,gBAAgB,KAAK,QAAQ,gBAAgB,gBAAgB,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAG,EACnF,aAAa,KAAK,KACpB,CAAC;GACD;EACF;CACF;CAEA,gBAAgB;EACd,OAAO;GACL,UAAU;IACR,SAAS;IACT,YAAY,YAAY,SAAS,SAAS,YAAY,UAAU;IAChE,aAAa,EAAE,eAAe,qBAAqB,YAAY,QAAQ;GACzE;GACA,KAAK;IACH,SAAS;IACT,YAAY,YAAY,SAAS,SAAS,OAAO,KAAK;IACtD,aAAa,EAAE,UAAU,qBAAqB,OAAO,GAAG;GAC1D;GACA,QAAQ;IACN,SAAS;IACT,YAAY,YAAY,SAAS,SAAS,UAAU,QAAQ;IAC5D,aAAa,EAAE,aAAa,qBAAqB,UAAU,MAAM;GACnE;EACF;CACF;CAGA,cAAc;EACZ,OAAO;GACL,eACG,UAAU,eACV,EAAE,OAAO,YAAY;IACpB,MAAM,EAAE,UAAU,MAAM;IAKxB,IAJuB,MAAM,KAAK,EAAE,QAAQ,MAAM,MAAM,IAAI,GAAG,UAAU,QAAQ,CAAC,CAAC,CAAC,MACjF,UAAU,MAAM,KAAK,KAAK,CAAC,CAAC,KAAK,SAAS,KAAK,IAGjC,GAAG,OAAO;IAE3B,MAAM,QAAiC,CAAC;IAExC,IAAI,cAAc,OAAO,MAAM,MAAM,YAAY;SAC5C,MAAM,SAAS,YAAY;IAEhC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,IAAI;GACtD;GAEF,qBAEG,EAAE,YACD,MAAM,CAAC,CACJ,MAAM,CAAC,CACP,SAAS,EAAE,IAAI,UAAU,eAAe;IACvC,MAAM,EAAE,UAAU,GAAG;IACrB,MAAM,eAAe,GAAG,qBAAqB,gBAAgB,GAAG,UAAU,OAAO;IAEjF,IAAI,cAAc,KAAK,SAAS,KAAK,MAAM;KACzC,GAAG,YAAY,MAAM,KAAK,MAAM,MAAM,aAAa,UAAU,aAAa,OAAO;KACjF,SAAS,iBAAiB,MAAM,MAAM,CAAC;KACvC,WAAW,EAAE;KACb,OAAO;IACT;IAEA,KAAK,IAAI,QAAQ,MAAM,OAAO,QAAQ,GAAG,SAAS,GAAG;KACnD,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,KAAK,SAAS,KAAK,MAAM;KAE/C,MAAM,WAAW,MAAM;KACvB,GAAG,YAAY,MAAM,OAAO,KAAK,GAAG,MAAM,MAAM,KAAK,GAAG,MAAM,KAAK,KAAK,CAAC,CAAC,OAAO;KACjF,SAAS,iBAAiB,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAC;KACtD,WAAW,EAAE;KACb,OAAO;IACT;IAEA,OAAO;GACT,CAAC,CAAC,CACD,IAAI;EACb;CACF;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,42 @@
1
1
  {
2
2
  "name": "@aysys/extension-div",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Tiptap に div、レスポンシブグリッド、sticky ノードを追加する拡張機能",
5
+ "license": "MIT",
6
+ "author": "AY-systems",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/AY-systems/editor-extensions.git",
10
+ "directory": "packages/extension-div"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md"
15
+ ],
16
+ "type": "module",
17
+ "main": "dist/index.cjs",
18
+ "module": "dist/index.mjs",
19
+ "types": "dist/index.d.mts",
20
+ "exports": {
21
+ ".": {
22
+ "types": {
23
+ "import": "./dist/index.d.mts",
24
+ "require": "./dist/index.d.cts"
25
+ },
26
+ "import": "./dist/index.mjs",
27
+ "require": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "devDependencies": {
31
+ "@tiptap/core": "^3.31.0",
32
+ "@tiptap/pm": "^3.31.0",
33
+ "vite-plus": "1.0.0"
34
+ },
35
+ "peerDependencies": {
36
+ "@tiptap/core": "^3.31.0",
37
+ "@tiptap/pm": "^3.31.0"
38
+ },
39
+ "scripts": {
40
+ "build": "vp pack"
41
+ }
6
42
  }