@vendure/dashboard 3.4.3-master-202509180227 → 3.4.3-master-202509200226

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 (54) hide show
  1. package/dist/vite/vite-plugin-config.js +1 -0
  2. package/package.json +11 -7
  3. package/src/app/common/duplicate-bulk-action.tsx +37 -23
  4. package/src/app/common/duplicate-entity-dialog.tsx +117 -0
  5. package/src/app/routes/_authenticated/_administrators/administrators.tsx +1 -2
  6. package/src/app/routes/_authenticated/_assets/assets.graphql.ts +39 -0
  7. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +18 -7
  8. package/src/app/routes/_authenticated/_assets/components/asset-tag-filter.tsx +206 -0
  9. package/src/app/routes/_authenticated/_assets/components/asset-tags-editor.tsx +226 -0
  10. package/src/app/routes/_authenticated/_assets/components/manage-tags-dialog.tsx +217 -0
  11. package/src/app/routes/_authenticated/_channels/channels.tsx +1 -2
  12. package/src/app/routes/_authenticated/_collections/collections.tsx +2 -16
  13. package/src/app/routes/_authenticated/_countries/countries.tsx +1 -2
  14. package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +1 -2
  15. package/src/app/routes/_authenticated/_customers/customers.tsx +1 -2
  16. package/src/app/routes/_authenticated/_facets/facets.tsx +0 -1
  17. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +1 -2
  18. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +1 -2
  19. package/src/app/routes/_authenticated/_products/products.tsx +1 -2
  20. package/src/app/routes/_authenticated/_promotions/promotions.tsx +1 -2
  21. package/src/app/routes/_authenticated/_roles/roles.tsx +1 -2
  22. package/src/app/routes/_authenticated/_sellers/sellers.tsx +1 -2
  23. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +1 -2
  24. package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +1 -2
  25. package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +1 -2
  26. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +1 -2
  27. package/src/app/routes/_authenticated/_zones/zones.tsx +1 -2
  28. package/src/lib/components/data-input/rich-text-input.tsx +2 -115
  29. package/src/lib/components/data-table/data-table-bulk-actions.tsx +5 -14
  30. package/src/lib/components/data-table/use-all-bulk-actions.ts +19 -0
  31. package/src/lib/components/data-table/use-generated-columns.tsx +12 -3
  32. package/src/lib/components/layout/nav-main.tsx +50 -25
  33. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +1 -1
  34. package/src/lib/components/shared/asset/asset-gallery.tsx +83 -50
  35. package/src/lib/components/shared/paginated-list-data-table.tsx +1 -0
  36. package/src/lib/components/shared/rich-text-editor/image-dialog.tsx +223 -0
  37. package/src/lib/components/shared/rich-text-editor/link-dialog.tsx +151 -0
  38. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +439 -0
  39. package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +338 -0
  40. package/src/lib/components/shared/rich-text-editor/table-delete-menu.tsx +104 -0
  41. package/src/lib/components/shared/rich-text-editor/table-edit-icons.tsx +225 -0
  42. package/src/lib/components/shared/vendure-image.tsx +9 -1
  43. package/src/lib/framework/defaults.ts +24 -0
  44. package/src/lib/framework/extension-api/types/navigation.ts +8 -0
  45. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +26 -0
  46. package/src/lib/framework/page/list-page.tsx +7 -0
  47. package/src/lib/graphql/common-operations.ts +19 -0
  48. package/src/lib/graphql/fragments.ts +23 -13
  49. package/src/lib/hooks/use-custom-field-config.ts +19 -2
  50. package/src/lib/index.ts +0 -1
  51. package/src/lib/providers/channel-provider.tsx +22 -6
  52. package/src/lib/providers/server-config.tsx +1 -0
  53. package/src/app/routes/_authenticated/_collections/components/move-single-collection.tsx +0 -33
  54. package/src/lib/components/shared/asset/focal-point-control.tsx +0 -57
@@ -0,0 +1,338 @@
1
+ import { FloatingMenu } from '@tiptap/extension-floating-menu';
2
+ import Image from '@tiptap/extension-image';
3
+ import { TableKit } from '@tiptap/extension-table';
4
+ import { TextStyle } from '@tiptap/extension-text-style';
5
+ import { EditorContent, useEditor } from '@tiptap/react';
6
+ import StarterKit from '@tiptap/starter-kit';
7
+ import { useLayoutEffect, useRef } from 'react';
8
+ import { ResponsiveToolbar } from './responsive-toolbar.js';
9
+ import { TableDeleteMenu } from './table-delete-menu.js';
10
+ import { TableEditIcons } from './table-edit-icons.js';
11
+
12
+ const extensions = [
13
+ TextStyle.configure(),
14
+ StarterKit.configure({
15
+ bulletList: {
16
+ keepMarks: true,
17
+ keepAttributes: false,
18
+ },
19
+ orderedList: {
20
+ keepMarks: true,
21
+ keepAttributes: false,
22
+ },
23
+ link: {
24
+ openOnClick: false,
25
+ HTMLAttributes: {
26
+ class: 'text-primary underline underline-offset-2 cursor-pointer hover:text-primary/80',
27
+ },
28
+ validate: href => /^https?:\/\//.test(href),
29
+ },
30
+ }),
31
+ Image.configure({
32
+ inline: true,
33
+ allowBase64: true,
34
+ HTMLAttributes: {
35
+ class: 'rich-text-image',
36
+ },
37
+ }),
38
+ TableKit.configure({
39
+ tableCell: {
40
+ HTMLAttributes: {
41
+ class: 'rich-text-table-cell',
42
+ },
43
+ },
44
+ tableHeader: {
45
+ HTMLAttributes: {
46
+ class: 'rich-text-table-header',
47
+ },
48
+ },
49
+ }),
50
+ FloatingMenu.configure({
51
+ shouldShow: null, // Let individual floating menus control when they show
52
+ }),
53
+ ];
54
+
55
+ export interface RichTextEditorProps {
56
+ value: string;
57
+ onChange: (value: string) => void;
58
+ disabled?: boolean;
59
+ }
60
+
61
+ export function RichTextEditor({ value, onChange, disabled = false }: Readonly<RichTextEditorProps>) {
62
+ const isInternalUpdate = useRef(false);
63
+
64
+ const editor = useEditor({
65
+ parseOptions: {
66
+ preserveWhitespace: 'full',
67
+ },
68
+ extensions: extensions,
69
+ content: value,
70
+ editable: !disabled,
71
+ onUpdate: ({ editor }) => {
72
+ if (!disabled) {
73
+ isInternalUpdate.current = true;
74
+ const newValue = editor.getHTML();
75
+ if (value !== newValue) {
76
+ onChange(newValue);
77
+ }
78
+ }
79
+ },
80
+ editorProps: {
81
+ attributes: {
82
+ class: `rich-text-editor placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/10 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive field-sizing-content min-h-16 w-full bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm max-h-[500px] overflow-y-auto ${disabled ? 'cursor-not-allowed opacity-50' : ''}`,
83
+ },
84
+ },
85
+ });
86
+
87
+ useLayoutEffect(() => {
88
+ if (editor && !isInternalUpdate.current) {
89
+ const currentContent = editor.getHTML();
90
+ if (currentContent !== value) {
91
+ const { from, to } = editor.state.selection;
92
+ editor.commands.setContent(value, { emitUpdate: false });
93
+ editor.commands.setTextSelection({ from, to });
94
+ }
95
+ }
96
+ isInternalUpdate.current = false;
97
+ }, [value, editor]);
98
+
99
+ useLayoutEffect(() => {
100
+ if (editor) {
101
+ editor.setEditable(!disabled, false);
102
+ }
103
+ }, [disabled, editor]);
104
+
105
+ if (!editor) {
106
+ return null;
107
+ }
108
+
109
+ return (
110
+ <div className="border rounded-md overflow-hidden">
111
+ <ResponsiveToolbar editor={editor} disabled={disabled} />
112
+ <EditorContent editor={editor} />
113
+ <TableEditIcons editor={editor} disabled={disabled} />
114
+ <TableDeleteMenu editor={editor} disabled={disabled} />
115
+ <style>{`
116
+ .rich-text-editor h1 {
117
+ font-size: 2em;
118
+ font-weight: 700;
119
+ margin-top: 0.67em;
120
+ margin-bottom: 0.67em;
121
+ line-height: 1.2;
122
+ }
123
+ .rich-text-editor h2 {
124
+ font-size: 1.5em;
125
+ font-weight: 600;
126
+ margin-top: 0.83em;
127
+ margin-bottom: 0.83em;
128
+ line-height: 1.3;
129
+ }
130
+ .rich-text-editor h3 {
131
+ font-size: 1.17em;
132
+ font-weight: 600;
133
+ margin-top: 1em;
134
+ margin-bottom: 1em;
135
+ line-height: 1.4;
136
+ }
137
+ .rich-text-editor h4 {
138
+ font-size: 1em;
139
+ font-weight: 600;
140
+ margin-top: 1.33em;
141
+ margin-bottom: 1.33em;
142
+ line-height: 1.4;
143
+ }
144
+ .rich-text-editor h5 {
145
+ font-size: 0.83em;
146
+ font-weight: 600;
147
+ margin-top: 1.67em;
148
+ margin-bottom: 1.67em;
149
+ line-height: 1.5;
150
+ }
151
+ .rich-text-editor h6 {
152
+ font-size: 0.67em;
153
+ font-weight: 600;
154
+ margin-top: 2.33em;
155
+ margin-bottom: 2.33em;
156
+ line-height: 1.6;
157
+ }
158
+ .rich-text-editor p {
159
+ margin-top: 0;
160
+ margin-bottom: 1em;
161
+ line-height: 1.6;
162
+ }
163
+ .rich-text-editor strong,
164
+ .rich-text-editor b {
165
+ font-weight: 700;
166
+ }
167
+ .rich-text-editor em,
168
+ .rich-text-editor i {
169
+ font-style: italic;
170
+ }
171
+ .rich-text-editor s,
172
+ .rich-text-editor del,
173
+ .rich-text-editor strike {
174
+ text-decoration: line-through;
175
+ }
176
+ .rich-text-editor ul {
177
+ list-style-type: disc;
178
+ margin-top: 0;
179
+ margin-bottom: 1em;
180
+ padding-left: 2em;
181
+ }
182
+ .rich-text-editor ul ul {
183
+ list-style-type: circle;
184
+ }
185
+ .rich-text-editor ul ul ul {
186
+ list-style-type: square;
187
+ }
188
+ .rich-text-editor ol {
189
+ list-style-type: decimal;
190
+ margin-top: 0;
191
+ margin-bottom: 1em;
192
+ padding-left: 2em;
193
+ }
194
+ .rich-text-editor li {
195
+ margin-bottom: 0.25em;
196
+ line-height: 1.6;
197
+ }
198
+ .rich-text-editor li > p {
199
+ margin-bottom: 0.25em;
200
+ }
201
+ .rich-text-editor li:last-child {
202
+ margin-bottom: 0;
203
+ }
204
+ .rich-text-editor blockquote {
205
+ border-left: 4px solid hsl(var(--border));
206
+ margin: 1em 0;
207
+ padding-left: 1em;
208
+ font-style: italic;
209
+ color: hsl(var(--muted-foreground));
210
+ }
211
+ .rich-text-editor blockquote p {
212
+ margin-bottom: 0.5em;
213
+ }
214
+ .rich-text-editor blockquote p:last-child {
215
+ margin-bottom: 0;
216
+ }
217
+ .rich-text-editor a {
218
+ color: hsl(var(--primary));
219
+ text-decoration: underline;
220
+ text-underline-offset: 2px;
221
+ cursor: pointer;
222
+ }
223
+ .rich-text-editor a:hover {
224
+ opacity: 0.8;
225
+ }
226
+ .rich-text-editor img,
227
+ .rich-text-editor .rich-text-image {
228
+ max-width: 100%;
229
+ height: auto;
230
+ display: inline-block;
231
+ margin: 0.5em 0;
232
+ border-radius: 4px;
233
+ }
234
+ .rich-text-editor img.ProseMirror-selectednode,
235
+ .rich-text-editor .rich-text-image.ProseMirror-selectednode {
236
+ outline: 2px solid hsl(var(--primary));
237
+ outline-offset: 2px;
238
+ }
239
+ .rich-text-editor table {
240
+ border-collapse: separate;
241
+ border-spacing: 0;
242
+ table-layout: auto;
243
+ width: 100%;
244
+ margin: 1em 0;
245
+ overflow: hidden;
246
+ border: 2px solid var(--color-muted);
247
+ border-radius: 6px;
248
+ }
249
+ .rich-text-editor table colgroup,
250
+ .rich-text-editor table col {
251
+ display: none;
252
+ }
253
+ .rich-text-editor table td,
254
+ .rich-text-editor table th,
255
+ .rich-text-editor .rich-text-table-cell,
256
+ .rich-text-editor .rich-text-table-header {
257
+ min-width: 1em;
258
+ border-right: 1px solid var(--color-muted);
259
+ border-bottom: 1px solid var(--color-muted);
260
+ padding: 8px 12px;
261
+ vertical-align: top;
262
+ box-sizing: border-box;
263
+ position: relative;
264
+ background-color: hsl(var(--background));
265
+ }
266
+ .rich-text-editor table td:last-child,
267
+ .rich-text-editor table th:last-child {
268
+ border-right: none;
269
+ }
270
+ .rich-text-editor table tr:last-child td,
271
+ .rich-text-editor table tr:last-child th {
272
+ border-bottom: none;
273
+ }
274
+ .rich-text-editor table th,
275
+ .rich-text-editor .rich-text-table-header {
276
+ font-weight: 600;
277
+ text-align: left;
278
+ background-color: hsl(var(--muted));
279
+ }
280
+ .rich-text-editor table .selectedCell {
281
+ background-color: hsl(var(--accent));
282
+ }
283
+ .rich-text-editor table .column-resize-handle {
284
+ position: absolute;
285
+ right: -2px;
286
+ top: 0;
287
+ bottom: 0;
288
+ width: 4px;
289
+ background-color: hsl(var(--primary));
290
+ pointer-events: none;
291
+ }
292
+ .rich-text-editor table p {
293
+ margin: 0;
294
+ }
295
+ .rich-text-editor .tableWrapper {
296
+ overflow-x: auto;
297
+ }
298
+ .rich-text-editor .resize-cursor {
299
+ cursor: ew-resize;
300
+ cursor: col-resize;
301
+ }
302
+ .rich-text-editor code {
303
+ background-color: hsl(var(--muted));
304
+ border-radius: 3px;
305
+ font-family: 'Courier New', Courier, monospace;
306
+ padding: 0.2em 0.4em;
307
+ font-size: 0.9em;
308
+ }
309
+ .rich-text-editor pre {
310
+ background-color: hsl(var(--muted));
311
+ border-radius: 6px;
312
+ padding: 1em;
313
+ overflow-x: auto;
314
+ margin: 1em 0;
315
+ }
316
+ .rich-text-editor pre code {
317
+ background-color: transparent;
318
+ padding: 0;
319
+ font-size: 0.9em;
320
+ }
321
+ .rich-text-editor hr {
322
+ border: none;
323
+ border-top: 1px solid hsl(var(--border));
324
+ margin: 2em 0;
325
+ }
326
+ .rich-text-editor:focus {
327
+ outline: none;
328
+ }
329
+ .rich-text-editor > *:first-child {
330
+ margin-top: 0;
331
+ }
332
+ .rich-text-editor > *:last-child {
333
+ margin-bottom: 0;
334
+ }
335
+ `}</style>
336
+ </div>
337
+ );
338
+ }
@@ -0,0 +1,104 @@
1
+ import { Editor } from '@tiptap/react';
2
+ import { Trash2Icon } from 'lucide-react';
3
+ import { useEffect, useState } from 'react';
4
+ import { Button } from '../../ui/button.js';
5
+
6
+ export interface TableDeleteMenuProps {
7
+ editor: Editor | null;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ export function TableDeleteMenu({ editor, disabled }: Readonly<TableDeleteMenuProps>) {
12
+ const [tableRect, setTableRect] = useState<DOMRect | null>(null);
13
+
14
+ useEffect(() => {
15
+ if (!editor) return;
16
+
17
+ const updateTablePosition = () => {
18
+ if (!editor.isActive('table')) {
19
+ setTableRect(null);
20
+ return;
21
+ }
22
+
23
+ try {
24
+ const { selection } = editor.state;
25
+ const { $from } = selection;
26
+
27
+ // Find the table cell node
28
+ let cellPos = null;
29
+
30
+ for (let depth = $from.depth; depth >= 0; depth--) {
31
+ const node = $from.node(depth);
32
+ if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
33
+ cellPos = $from.before(depth);
34
+ break;
35
+ }
36
+ }
37
+
38
+ if (cellPos === null) {
39
+ setTableRect(null);
40
+ return;
41
+ }
42
+
43
+ // Get DOM elements
44
+ const cellDOMNode = editor.view.nodeDOM(cellPos) as HTMLElement;
45
+ const tableDOMNode = cellDOMNode?.closest('table');
46
+
47
+ if (!tableDOMNode) {
48
+ setTableRect(null);
49
+ return;
50
+ }
51
+
52
+ setTableRect(tableDOMNode.getBoundingClientRect());
53
+ } catch (error) {
54
+ console.warn('Error calculating table position:', error);
55
+ setTableRect(null);
56
+ }
57
+ };
58
+
59
+ // Update on selection change
60
+ const handleSelectionUpdate = () => {
61
+ updateTablePosition();
62
+ };
63
+
64
+ editor.on('selectionUpdate', handleSelectionUpdate);
65
+ editor.on('transaction', handleSelectionUpdate);
66
+
67
+ // Initial update
68
+ updateTablePosition();
69
+
70
+ return () => {
71
+ editor.off('selectionUpdate', handleSelectionUpdate);
72
+ editor.off('transaction', handleSelectionUpdate);
73
+ };
74
+ }, [editor]);
75
+
76
+ if (!tableRect || disabled || !editor) return null;
77
+
78
+ // Position at bottom right of table
79
+ const iconTop = tableRect.bottom - 30;
80
+ const iconLeft = tableRect.right - 30;
81
+
82
+ return (
83
+ <div
84
+ style={{
85
+ position: 'fixed',
86
+ top: iconTop,
87
+ left: iconLeft,
88
+ zIndex: 1000,
89
+ }}
90
+ >
91
+ <Button
92
+ type="button"
93
+ variant="outline"
94
+ size="sm"
95
+ onClick={() => editor.chain().focus().deleteTable().run()}
96
+ disabled={disabled || !editor.can().deleteTable()}
97
+ className="h-6 w-6 p-0 bg-background shadow-md hover:bg-destructive/10 text-destructive hover:text-destructive"
98
+ title="Delete Table"
99
+ >
100
+ <Trash2Icon className="h-3 w-3" />
101
+ </Button>
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1,225 @@
1
+ import { Trans } from '@/vdb/lib/trans.js';
2
+ import { Editor } from '@tiptap/react';
3
+ import { MoreHorizontalIcon, MoreVerticalIcon } from 'lucide-react';
4
+ import { useEffect, useState } from 'react';
5
+ import { Button } from '../../ui/button.js';
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from '../../ui/dropdown-menu.js';
13
+
14
+ export interface TableEditIconsProps {
15
+ editor: Editor | null;
16
+ disabled?: boolean;
17
+ }
18
+
19
+ interface TableCellPosition {
20
+ columnIndex: number;
21
+ rowIndex: number;
22
+ cellRect: DOMRect | null;
23
+ tableRect: DOMRect | null;
24
+ }
25
+
26
+ export function TableEditIcons({ editor, disabled }: Readonly<TableEditIconsProps>) {
27
+ const [cellPosition, setCellPosition] = useState<TableCellPosition | null>(null);
28
+
29
+ useEffect(() => {
30
+ if (!editor) return;
31
+
32
+ const updateCellPosition = () => {
33
+ if (!editor.isActive('table')) {
34
+ setCellPosition(null);
35
+ return;
36
+ }
37
+
38
+ try {
39
+ const { selection } = editor.state;
40
+ const { $from } = selection;
41
+
42
+ // Find the table cell node
43
+ let cellNode = null;
44
+ let cellPos = null;
45
+
46
+ for (let depth = $from.depth; depth >= 0; depth--) {
47
+ const node = $from.node(depth);
48
+ if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
49
+ cellNode = node;
50
+ cellPos = $from.before(depth);
51
+ break;
52
+ }
53
+ }
54
+
55
+ if (!cellNode || cellPos === null) {
56
+ setCellPosition(null);
57
+ return;
58
+ }
59
+
60
+ // Get DOM elements
61
+ const cellDOMNode = editor.view.nodeDOM(cellPos) as HTMLElement;
62
+ const tableDOMNode = cellDOMNode?.closest('table');
63
+
64
+ if (!cellDOMNode || !tableDOMNode) {
65
+ setCellPosition(null);
66
+ return;
67
+ }
68
+
69
+ // Calculate column and row indices
70
+ const row = cellDOMNode.closest('tr');
71
+ const columnIndex = Array.from(row?.children || []).indexOf(cellDOMNode);
72
+ const rowIndex = Array.from(tableDOMNode.querySelectorAll('tr')).indexOf(row!);
73
+
74
+ setCellPosition({
75
+ columnIndex,
76
+ rowIndex,
77
+ cellRect: cellDOMNode.getBoundingClientRect(),
78
+ tableRect: tableDOMNode.getBoundingClientRect(),
79
+ });
80
+ } catch (error) {
81
+ console.warn('Error calculating table cell position:', error);
82
+ setCellPosition(null);
83
+ }
84
+ };
85
+
86
+ // Update on selection change
87
+ const handleSelectionUpdate = () => {
88
+ updateCellPosition();
89
+ };
90
+
91
+ editor.on('selectionUpdate', handleSelectionUpdate);
92
+ editor.on('transaction', handleSelectionUpdate);
93
+
94
+ // Initial update
95
+ updateCellPosition();
96
+
97
+ return () => {
98
+ editor.off('selectionUpdate', handleSelectionUpdate);
99
+ editor.off('transaction', handleSelectionUpdate);
100
+ };
101
+ }, [editor]);
102
+
103
+ if (!cellPosition || disabled) return null;
104
+
105
+ if (!editor) return null;
106
+
107
+ const { cellRect, tableRect } = cellPosition;
108
+
109
+ if (!cellRect || !tableRect) return null;
110
+
111
+ // Calculate positions for edit icons
112
+ const columnIconTop = tableRect.top - 30;
113
+ const columnIconLeft = cellRect.left + cellRect.width / 2 - 12;
114
+
115
+ const rowIconTop = cellRect.top + cellRect.height / 2 - 12;
116
+ const rowIconLeft = tableRect.left - 30;
117
+
118
+ return (
119
+ <>
120
+ {/* Column edit icon */}
121
+ <div
122
+ style={{
123
+ position: 'fixed',
124
+ top: columnIconTop,
125
+ left: columnIconLeft,
126
+ zIndex: 1000,
127
+ }}
128
+ >
129
+ <DropdownMenu>
130
+ <DropdownMenuTrigger asChild>
131
+ <Button
132
+ type="button"
133
+ variant="ghost"
134
+ size="sm"
135
+ className="h-6 w-6 p-0 bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground border-0 shadow-sm"
136
+ >
137
+ <MoreHorizontalIcon className="h-3 w-3" />
138
+ </Button>
139
+ </DropdownMenuTrigger>
140
+ <DropdownMenuContent align="center">
141
+ <DropdownMenuItem
142
+ onClick={() => editor.chain().focus().addColumnBefore().run()}
143
+ disabled={!editor.can().addColumnBefore()}
144
+ >
145
+ <Trans>Add column before</Trans>
146
+ </DropdownMenuItem>
147
+ <DropdownMenuItem
148
+ onClick={() => editor.chain().focus().addColumnAfter().run()}
149
+ disabled={!editor.can().addColumnAfter()}
150
+ >
151
+ <Trans>Add column after</Trans>
152
+ </DropdownMenuItem>
153
+ <DropdownMenuSeparator />
154
+ <DropdownMenuItem
155
+ onClick={() => editor.chain().focus().toggleHeaderColumn().run()}
156
+ disabled={!editor.can().toggleHeaderColumn()}
157
+ >
158
+ <Trans>Toggle header column</Trans>
159
+ </DropdownMenuItem>
160
+ <DropdownMenuSeparator />
161
+ <DropdownMenuItem
162
+ onClick={() => editor.chain().focus().deleteColumn().run()}
163
+ disabled={!editor.can().deleteColumn()}
164
+ className="text-destructive focus:text-destructive"
165
+ >
166
+ <Trans>Delete column</Trans>
167
+ </DropdownMenuItem>
168
+ </DropdownMenuContent>
169
+ </DropdownMenu>
170
+ </div>
171
+
172
+ {/* Row edit icon */}
173
+ <div
174
+ style={{
175
+ position: 'fixed',
176
+ top: rowIconTop,
177
+ left: rowIconLeft,
178
+ zIndex: 1000,
179
+ }}
180
+ >
181
+ <DropdownMenu>
182
+ <DropdownMenuTrigger asChild>
183
+ <Button
184
+ type="button"
185
+ variant="ghost"
186
+ size="sm"
187
+ className="h-6 w-6 p-0 bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground border-0 shadow-sm"
188
+ >
189
+ <MoreVerticalIcon className="h-3 w-3" />
190
+ </Button>
191
+ </DropdownMenuTrigger>
192
+ <DropdownMenuContent align="center">
193
+ <DropdownMenuItem
194
+ onClick={() => editor.chain().focus().addRowBefore().run()}
195
+ disabled={!editor.can().addRowBefore()}
196
+ >
197
+ <Trans>Add row before</Trans>
198
+ </DropdownMenuItem>
199
+ <DropdownMenuItem
200
+ onClick={() => editor.chain().focus().addRowAfter().run()}
201
+ disabled={!editor.can().addRowAfter()}
202
+ >
203
+ <Trans>Add row after</Trans>
204
+ </DropdownMenuItem>
205
+ <DropdownMenuSeparator />
206
+ <DropdownMenuItem
207
+ onClick={() => editor.chain().focus().toggleHeaderRow().run()}
208
+ disabled={!editor.can().toggleHeaderRow()}
209
+ >
210
+ <Trans>Toggle header row</Trans>
211
+ </DropdownMenuItem>
212
+ <DropdownMenuSeparator />
213
+ <DropdownMenuItem
214
+ onClick={() => editor.chain().focus().deleteRow().run()}
215
+ disabled={!editor.can().deleteRow()}
216
+ className="text-destructive focus:text-destructive"
217
+ >
218
+ <Trans>Delete row</Trans>
219
+ </DropdownMenuItem>
220
+ </DropdownMenuContent>
221
+ </DropdownMenu>
222
+ </div>
223
+ </>
224
+ );
225
+ }
@@ -25,7 +25,7 @@ export interface AssetLike {
25
25
  * @docsPage VendureImage
26
26
  * @since 3.4.0
27
27
  */
28
- export type ImagePreset = 'tiny' | 'thumb' | 'small' | 'medium' | 'large' | null;
28
+ export type ImagePreset = 'tiny' | 'thumb' | 'small' | 'medium' | 'large' | 'full' | null;
29
29
 
30
30
  /**
31
31
  * @description
@@ -219,6 +219,10 @@ function getMinDimensions(preset?: ImagePreset, width?: number, height?: number)
219
219
  return { width: 300, height: 300 };
220
220
  case 'medium':
221
221
  return { width: 500, height: 500 };
222
+ case 'large':
223
+ return { width: 800, height: 800 };
224
+ case 'full':
225
+ return { width: undefined, height: undefined };
222
226
  }
223
227
  }
224
228
 
@@ -258,6 +262,10 @@ export function PlaceholderImage({
258
262
  width = 800;
259
263
  height = 800;
260
264
  break;
265
+ case 'full':
266
+ width = 1200;
267
+ height = 1200;
268
+ break;
261
269
  default:
262
270
  break;
263
271
  }