@tuturuuu/ui 0.7.0 → 0.9.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/CHANGELOG.md +88 -0
- package/biome.json +1 -1
- package/package.json +75 -73
- package/src/components/ui/accordion.tsx +1 -1
- package/src/components/ui/breadcrumb.tsx +1 -1
- package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
- package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
- package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
- package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/carousel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
- package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
- package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/color-picker.tsx +1 -1
- package/src/components/ui/command.tsx +1 -1
- package/src/components/ui/context-menu.tsx +5 -1
- package/src/components/ui/currency-input.test.tsx +43 -0
- package/src/components/ui/currency-input.tsx +1 -1
- package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
- package/src/components/ui/custom/combobox.test.tsx +195 -0
- package/src/components/ui/custom/combobox.tsx +273 -156
- package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
- package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
- package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
- package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
- package/src/components/ui/custom/theme-toggle.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
- package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
- package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
- package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
- package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
- package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
- package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
- package/src/components/ui/custom/workspace-select.tsx +8 -3
- package/src/components/ui/dialog.test.tsx +52 -0
- package/src/components/ui/dialog.tsx +6 -2
- package/src/components/ui/dropdown-menu.tsx +5 -1
- package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
- package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
- package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
- package/src/components/ui/finance/debts/debts-page.tsx +15 -2
- package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
- package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
- package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
- package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/utils.ts +3 -1
- package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
- package/src/components/ui/finance/transactions/form-types.ts +3 -0
- package/src/components/ui/finance/transactions/form.tsx +2 -0
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
- package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
- package/src/components/ui/finance/transactions/transaction-card.tsx +21 -9
- package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
- package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
- package/src/components/ui/finance/wallets/form.test.tsx +51 -3
- package/src/components/ui/finance/wallets/form.tsx +15 -4
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
- package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
- package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
- package/src/components/ui/input-otp.tsx +1 -1
- package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
- package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
- package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
- package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
- package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
- package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
- package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
- package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
- package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
- package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
- package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
- package/src/components/ui/money-input.test.tsx +64 -0
- package/src/components/ui/money-input.tsx +63 -0
- package/src/components/ui/navigation-menu.tsx +1 -1
- package/src/components/ui/pagination.tsx +1 -1
- package/src/components/ui/radio-group.tsx +1 -1
- package/src/components/ui/select.tsx +5 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/ui/storefront/cart-popover.tsx +61 -0
- package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
- package/src/components/ui/storefront/cart-summary.tsx +104 -80
- package/src/components/ui/storefront/checkout-overlay.tsx +26 -0
- package/src/components/ui/storefront/hero-panel.tsx +2 -8
- package/src/components/ui/storefront/image-panel.tsx +6 -0
- package/src/components/ui/storefront/index.ts +11 -0
- package/src/components/ui/storefront/listing-card.tsx +84 -22
- package/src/components/ui/storefront/merch-sections.tsx +70 -0
- package/src/components/ui/storefront/product-detail.tsx +289 -0
- package/src/components/ui/storefront/product-dialog.tsx +72 -0
- package/src/components/ui/storefront/storefront-surface.test.tsx +221 -3
- package/src/components/ui/storefront/storefront-surface.tsx +288 -153
- package/src/components/ui/storefront/types.ts +27 -1
- package/src/components/ui/storefront/utils.ts +117 -27
- package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
- package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
- package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
- package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -0
- package/src/components/ui/text-editor/background-color-extension.ts +62 -0
- package/src/components/ui/text-editor/color-controls.tsx +284 -0
- package/src/components/ui/text-editor/content-migration.ts +41 -18
- package/src/components/ui/text-editor/editor.tsx +69 -14
- package/src/components/ui/text-editor/extensions.ts +9 -3
- package/src/components/ui/text-editor/highlight-extension.ts +22 -0
- package/src/components/ui/text-editor/image-extension.ts +40 -18
- package/src/components/ui/text-editor/tool-bar.tsx +9 -16
- package/src/components/ui/text-editor/video-extension.ts +11 -2
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
- package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
- package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
- package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
- package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +113 -46
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +51 -9
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +127 -38
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +410 -4
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +106 -14
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +186 -0
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +59 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
- package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
- package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
- package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
- package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
- package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
- package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
- package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
- package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
- package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
- package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
- package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
- package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +237 -3
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
- package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
- package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
- package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +465 -937
- package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
- package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
- package/src/components/ui/tu-do/shared/board-views.tsx +596 -82
- package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
- package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
- package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
- package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
- package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
- package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +53 -0
- package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +44 -15
- package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
- package/src/declarations.d.ts +1 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
- package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
- package/src/hooks/__tests__/useBoardRealtime.test.tsx +2 -2
- package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
- package/src/hooks/use-calendar-sync.tsx +247 -243
- package/src/hooks/use-calendar.tsx +323 -138
- package/src/hooks/use-task-actions.ts +24 -0
- package/src/hooks/use-user-workspace-config.ts +75 -0
- package/src/hooks/use-workspace-currency.ts +8 -3
- package/src/hooks/useBoardRealtime.ts +6 -3
- package/src/hooks/useBoardRealtime.types.ts +11 -0
- package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
- package/src/hooks/useCursorTracking.ts +91 -27
- package/src/hooks/useTaskUserRealtime.ts +5 -3
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/react';
|
|
2
|
+
import { Highlighter, Paintbrush, TextIcon, X } from '@tuturuuu/icons';
|
|
3
|
+
import { Button } from '@tuturuuu/ui/button';
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@tuturuuu/ui/popover';
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
|
|
6
|
+
import { cn } from '@tuturuuu/utils/format';
|
|
7
|
+
|
|
8
|
+
interface EditorColorSwatch {
|
|
9
|
+
name: string;
|
|
10
|
+
foreground: string;
|
|
11
|
+
background: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const EDITOR_COLOR_SWATCHES: EditorColorSwatch[] = [
|
|
15
|
+
{
|
|
16
|
+
name: 'Yellow',
|
|
17
|
+
foreground: 'var(--yellow)',
|
|
18
|
+
background: 'var(--calendar-bg-yellow)',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'Orange',
|
|
22
|
+
foreground: 'var(--orange)',
|
|
23
|
+
background: 'var(--calendar-bg-orange)',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'Red',
|
|
27
|
+
foreground: 'var(--red)',
|
|
28
|
+
background: 'var(--calendar-bg-red)',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Pink',
|
|
32
|
+
foreground: 'var(--pink)',
|
|
33
|
+
background: 'var(--calendar-bg-pink)',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Purple',
|
|
37
|
+
foreground: 'var(--purple)',
|
|
38
|
+
background: 'var(--calendar-bg-purple)',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Blue',
|
|
42
|
+
foreground: 'var(--blue)',
|
|
43
|
+
background: 'var(--calendar-bg-blue)',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Green',
|
|
47
|
+
foreground: 'var(--green)',
|
|
48
|
+
background: 'var(--calendar-bg-green)',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'Gray',
|
|
52
|
+
foreground: 'var(--gray)',
|
|
53
|
+
background: 'var(--calendar-bg-gray)',
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
type HighlightAttributes = {
|
|
58
|
+
color: string;
|
|
59
|
+
textColor?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type TextStyleAttributes = {
|
|
63
|
+
color?: string | null;
|
|
64
|
+
backgroundColor?: string | null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
interface TextEditorColorControlsProps {
|
|
68
|
+
editor: Editor | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function TextEditorColorControls({
|
|
72
|
+
editor,
|
|
73
|
+
}: TextEditorColorControlsProps) {
|
|
74
|
+
if (!editor) return null;
|
|
75
|
+
|
|
76
|
+
const getAttributes = editor.getAttributes?.bind(editor);
|
|
77
|
+
const highlightAttributes = (getAttributes?.('highlight') ?? {}) as {
|
|
78
|
+
color?: string | null;
|
|
79
|
+
textColor?: string | null;
|
|
80
|
+
};
|
|
81
|
+
const textStyleAttributes = (getAttributes?.('textStyle') ??
|
|
82
|
+
{}) as TextStyleAttributes;
|
|
83
|
+
|
|
84
|
+
const textColor = textStyleAttributes.color ?? null;
|
|
85
|
+
const backgroundColor = textStyleAttributes.backgroundColor ?? null;
|
|
86
|
+
const highlightColor = highlightAttributes.color ?? null;
|
|
87
|
+
const highlightTextColor = highlightAttributes.textColor ?? null;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<ColorControlPopover
|
|
92
|
+
active={editor.isActive('highlight')}
|
|
93
|
+
clearLabel="Clear highlight"
|
|
94
|
+
indicatorBackground={highlightColor}
|
|
95
|
+
indicatorColor={highlightTextColor}
|
|
96
|
+
label="Highlight color"
|
|
97
|
+
icon={<Highlighter className="size-4" />}
|
|
98
|
+
onClear={() => editor.chain().focus().unsetHighlight().run()}
|
|
99
|
+
swatchKind="highlight"
|
|
100
|
+
onSelect={(swatch) => {
|
|
101
|
+
const attributes: HighlightAttributes = {
|
|
102
|
+
color: swatch.background,
|
|
103
|
+
textColor: swatch.foreground,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
editor.chain().focus().setHighlight(attributes).run();
|
|
107
|
+
}}
|
|
108
|
+
isSwatchActive={(swatch) =>
|
|
109
|
+
editor.isActive('highlight', {
|
|
110
|
+
color: swatch.background,
|
|
111
|
+
textColor: swatch.foreground,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
<ColorControlPopover
|
|
116
|
+
active={Boolean(textColor)}
|
|
117
|
+
clearLabel="Clear text color"
|
|
118
|
+
indicatorColor={textColor}
|
|
119
|
+
label="Text color"
|
|
120
|
+
icon={<TextIcon className="size-4" />}
|
|
121
|
+
onClear={() => editor.chain().focus().unsetColor().run()}
|
|
122
|
+
swatchKind="text"
|
|
123
|
+
onSelect={(swatch) =>
|
|
124
|
+
editor.chain().focus().setColor(swatch.foreground).run()
|
|
125
|
+
}
|
|
126
|
+
isSwatchActive={(swatch) =>
|
|
127
|
+
editor.isActive('textStyle', { color: swatch.foreground })
|
|
128
|
+
}
|
|
129
|
+
/>
|
|
130
|
+
<ColorControlPopover
|
|
131
|
+
active={Boolean(backgroundColor)}
|
|
132
|
+
clearLabel="Clear background color"
|
|
133
|
+
indicatorBackground={backgroundColor}
|
|
134
|
+
label="Background color"
|
|
135
|
+
icon={<Paintbrush className="size-4" />}
|
|
136
|
+
onClear={() => editor.chain().focus().unsetBackgroundColor().run()}
|
|
137
|
+
swatchKind="background"
|
|
138
|
+
onSelect={(swatch) =>
|
|
139
|
+
editor.chain().focus().setBackgroundColor(swatch.background).run()
|
|
140
|
+
}
|
|
141
|
+
isSwatchActive={(swatch) =>
|
|
142
|
+
editor.isActive('textStyle', {
|
|
143
|
+
backgroundColor: swatch.background,
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
/>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface ColorControlPopoverProps {
|
|
152
|
+
active: boolean;
|
|
153
|
+
clearLabel: string;
|
|
154
|
+
icon: React.ReactNode;
|
|
155
|
+
indicatorBackground?: string | null;
|
|
156
|
+
indicatorColor?: string | null;
|
|
157
|
+
isSwatchActive: (swatch: EditorColorSwatch) => boolean;
|
|
158
|
+
label: string;
|
|
159
|
+
onClear: () => void;
|
|
160
|
+
onSelect: (swatch: EditorColorSwatch) => void;
|
|
161
|
+
swatchKind: 'highlight' | 'text' | 'background';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function ColorControlPopover({
|
|
165
|
+
active,
|
|
166
|
+
clearLabel,
|
|
167
|
+
icon,
|
|
168
|
+
indicatorBackground,
|
|
169
|
+
indicatorColor,
|
|
170
|
+
isSwatchActive,
|
|
171
|
+
label,
|
|
172
|
+
onClear,
|
|
173
|
+
onSelect,
|
|
174
|
+
swatchKind,
|
|
175
|
+
}: ColorControlPopoverProps) {
|
|
176
|
+
return (
|
|
177
|
+
<Popover>
|
|
178
|
+
<Tooltip>
|
|
179
|
+
<TooltipTrigger asChild>
|
|
180
|
+
<PopoverTrigger asChild>
|
|
181
|
+
<Button
|
|
182
|
+
aria-label={label}
|
|
183
|
+
aria-pressed={active}
|
|
184
|
+
className={cn(
|
|
185
|
+
'relative h-8 w-8 rounded-md border border-transparent transition-colors hover:bg-dynamic-surface/80',
|
|
186
|
+
active &&
|
|
187
|
+
'border-foreground/10 bg-dynamic-surface/80 text-foreground'
|
|
188
|
+
)}
|
|
189
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
190
|
+
size="icon"
|
|
191
|
+
type="button"
|
|
192
|
+
variant="ghost"
|
|
193
|
+
>
|
|
194
|
+
{icon}
|
|
195
|
+
<span
|
|
196
|
+
className="pointer-events-none absolute right-1 bottom-1 h-1.5 w-4 rounded-full border border-background"
|
|
197
|
+
style={{
|
|
198
|
+
backgroundColor:
|
|
199
|
+
indicatorBackground ?? indicatorColor ?? 'transparent',
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
</Button>
|
|
203
|
+
</PopoverTrigger>
|
|
204
|
+
</TooltipTrigger>
|
|
205
|
+
<TooltipContent side="bottom">{label}</TooltipContent>
|
|
206
|
+
</Tooltip>
|
|
207
|
+
<PopoverContent align="start" className="w-56 p-2">
|
|
208
|
+
<div className="space-y-2">
|
|
209
|
+
<div className="font-medium text-muted-foreground text-xs">
|
|
210
|
+
{label}
|
|
211
|
+
</div>
|
|
212
|
+
<div className="grid grid-cols-4 gap-1.5">
|
|
213
|
+
{EDITOR_COLOR_SWATCHES.map((swatch) => (
|
|
214
|
+
<SwatchButton
|
|
215
|
+
active={isSwatchActive(swatch)}
|
|
216
|
+
key={`${swatchKind}-${swatch.name}`}
|
|
217
|
+
swatch={swatch}
|
|
218
|
+
swatchKind={swatchKind}
|
|
219
|
+
onSelect={() => onSelect(swatch)}
|
|
220
|
+
/>
|
|
221
|
+
))}
|
|
222
|
+
</div>
|
|
223
|
+
<Button
|
|
224
|
+
className="h-8 w-full justify-start px-2 text-xs"
|
|
225
|
+
onClick={onClear}
|
|
226
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
227
|
+
type="button"
|
|
228
|
+
variant="ghost"
|
|
229
|
+
>
|
|
230
|
+
<X className="mr-1.5 size-3.5" />
|
|
231
|
+
{clearLabel}
|
|
232
|
+
</Button>
|
|
233
|
+
</div>
|
|
234
|
+
</PopoverContent>
|
|
235
|
+
</Popover>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface SwatchButtonProps {
|
|
240
|
+
active: boolean;
|
|
241
|
+
onSelect: () => void;
|
|
242
|
+
swatch: EditorColorSwatch;
|
|
243
|
+
swatchKind: 'highlight' | 'text' | 'background';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function SwatchButton({
|
|
247
|
+
active,
|
|
248
|
+
onSelect,
|
|
249
|
+
swatch,
|
|
250
|
+
swatchKind,
|
|
251
|
+
}: SwatchButtonProps) {
|
|
252
|
+
const backgroundColor =
|
|
253
|
+
swatchKind === 'text' ? 'var(--background)' : swatch.background;
|
|
254
|
+
const color =
|
|
255
|
+
swatchKind === 'background' ? 'var(--foreground)' : swatch.foreground;
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<Button
|
|
259
|
+
aria-label={`${swatch.name} ${swatchKind}`}
|
|
260
|
+
className={cn(
|
|
261
|
+
'h-8 w-8 rounded-md border border-dynamic-border p-0 transition-all hover:scale-105',
|
|
262
|
+
active &&
|
|
263
|
+
'ring-2 ring-foreground/30 ring-offset-1 ring-offset-background'
|
|
264
|
+
)}
|
|
265
|
+
onClick={onSelect}
|
|
266
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
267
|
+
size="icon"
|
|
268
|
+
style={{ backgroundColor, color }}
|
|
269
|
+
type="button"
|
|
270
|
+
variant="ghost"
|
|
271
|
+
>
|
|
272
|
+
{swatchKind === 'text' ? (
|
|
273
|
+
<TextIcon className="size-4" />
|
|
274
|
+
) : (
|
|
275
|
+
<span
|
|
276
|
+
className="size-4 rounded-sm border border-foreground/10"
|
|
277
|
+
style={{
|
|
278
|
+
backgroundColor: swatch.background,
|
|
279
|
+
}}
|
|
280
|
+
/>
|
|
281
|
+
)}
|
|
282
|
+
</Button>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
@@ -6,6 +6,10 @@ import type { JSONContent } from '@tiptap/react';
|
|
|
6
6
|
*/
|
|
7
7
|
const IMAGE_NODE_TYPES = ['image', 'imageResize'];
|
|
8
8
|
|
|
9
|
+
function getContentChildren(node: JSONContent): JSONContent[] | null {
|
|
10
|
+
return Array.isArray(node.content) ? node.content : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* Migrates content from inline images (inside paragraphs) to block-level images.
|
|
11
15
|
* This ensures backward compatibility when switching from inline: true to inline: false.
|
|
@@ -36,13 +40,19 @@ const IMAGE_NODE_TYPES = ['image', 'imageResize'];
|
|
|
36
40
|
export function migrateInlineImagesToBlock(
|
|
37
41
|
content: JSONContent | null
|
|
38
42
|
): JSONContent | null {
|
|
39
|
-
if (!content
|
|
43
|
+
if (!content) return content;
|
|
44
|
+
|
|
45
|
+
const contentChildren = getContentChildren(content);
|
|
46
|
+
if (!contentChildren) return content;
|
|
40
47
|
|
|
41
48
|
const newContent: JSONContent[] = [];
|
|
42
49
|
|
|
43
|
-
for (const node of
|
|
50
|
+
for (const node of contentChildren) {
|
|
44
51
|
// Recursively migrate nested structures (lists, blockquotes, etc.)
|
|
45
|
-
if (
|
|
52
|
+
if (
|
|
53
|
+
getContentChildren(node) &&
|
|
54
|
+
!IMAGE_NODE_TYPES.includes(node.type || '')
|
|
55
|
+
) {
|
|
46
56
|
const migratedNode = migrateNodeContent(node);
|
|
47
57
|
if (migratedNode.extractedImages.length > 0) {
|
|
48
58
|
// Add the node with non-image content (if it has any)
|
|
@@ -74,18 +84,23 @@ interface MigratedNode {
|
|
|
74
84
|
* Recursively migrates a node's content, extracting inline images from paragraphs.
|
|
75
85
|
*/
|
|
76
86
|
function migrateNodeContent(node: JSONContent): MigratedNode {
|
|
87
|
+
const contentChildren = getContentChildren(node);
|
|
88
|
+
|
|
77
89
|
// Handle paragraph nodes - extract inline images
|
|
78
|
-
if (node.type === 'paragraph' &&
|
|
90
|
+
if (node.type === 'paragraph' && contentChildren?.length) {
|
|
79
91
|
return extractImagesFromParagraph(node);
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
// Handle container nodes (list items, blockquotes, table cells, etc.) - recurse into children
|
|
83
|
-
if (
|
|
95
|
+
if (contentChildren?.length) {
|
|
84
96
|
const migratedChildren: JSONContent[] = [];
|
|
85
97
|
const allExtractedImages: JSONContent[] = [];
|
|
86
98
|
|
|
87
|
-
for (const child of
|
|
88
|
-
if (
|
|
99
|
+
for (const child of contentChildren) {
|
|
100
|
+
if (
|
|
101
|
+
getContentChildren(child) &&
|
|
102
|
+
!IMAGE_NODE_TYPES.includes(child.type || '')
|
|
103
|
+
) {
|
|
89
104
|
const result = migrateNodeContent(child);
|
|
90
105
|
|
|
91
106
|
if (result.extractedImages.length > 0) {
|
|
@@ -116,14 +131,16 @@ function migrateNodeContent(node: JSONContent): MigratedNode {
|
|
|
116
131
|
* and the extracted images separately.
|
|
117
132
|
*/
|
|
118
133
|
function extractImagesFromParagraph(paragraph: JSONContent): MigratedNode {
|
|
119
|
-
|
|
134
|
+
const contentChildren = getContentChildren(paragraph);
|
|
135
|
+
|
|
136
|
+
if (!contentChildren) {
|
|
120
137
|
return { node: paragraph, extractedImages: [] };
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
const textContent: JSONContent[] = [];
|
|
124
141
|
const images: JSONContent[] = [];
|
|
125
142
|
|
|
126
|
-
for (const child of
|
|
143
|
+
for (const child of contentChildren) {
|
|
127
144
|
if (IMAGE_NODE_TYPES.includes(child.type || '')) {
|
|
128
145
|
images.push(child);
|
|
129
146
|
} else {
|
|
@@ -148,9 +165,10 @@ function extractImagesFromParagraph(paragraph: JSONContent): MigratedNode {
|
|
|
148
165
|
* Empty paragraphs should be filtered out when all content was extracted.
|
|
149
166
|
*/
|
|
150
167
|
function hasNonEmptyContent(node: JSONContent): boolean {
|
|
151
|
-
|
|
168
|
+
const contentChildren = getContentChildren(node);
|
|
169
|
+
if (!contentChildren || contentChildren.length === 0) return false;
|
|
152
170
|
|
|
153
|
-
return
|
|
171
|
+
return contentChildren.some((child) => {
|
|
154
172
|
// Check for text content
|
|
155
173
|
if (child.text && child.text.trim().length > 0) return true;
|
|
156
174
|
|
|
@@ -165,7 +183,7 @@ function hasNonEmptyContent(node: JSONContent): boolean {
|
|
|
165
183
|
}
|
|
166
184
|
|
|
167
185
|
// Recursively check children
|
|
168
|
-
if (child
|
|
186
|
+
if (getContentChildren(child)) return hasNonEmptyContent(child);
|
|
169
187
|
|
|
170
188
|
return false;
|
|
171
189
|
});
|
|
@@ -176,24 +194,29 @@ function hasNonEmptyContent(node: JSONContent): boolean {
|
|
|
176
194
|
* Used to avoid unnecessary processing.
|
|
177
195
|
*/
|
|
178
196
|
export function needsMigration(content: JSONContent | null): boolean {
|
|
179
|
-
if (!content
|
|
197
|
+
if (!content) return false;
|
|
198
|
+
|
|
199
|
+
const contentChildren = getContentChildren(content);
|
|
200
|
+
if (!contentChildren) return false;
|
|
180
201
|
|
|
181
202
|
function checkNode(node: JSONContent): boolean {
|
|
203
|
+
const nodeChildren = getContentChildren(node);
|
|
204
|
+
|
|
182
205
|
// Check if this is a paragraph with inline images
|
|
183
|
-
if (node.type === 'paragraph' &&
|
|
184
|
-
const hasImage =
|
|
206
|
+
if (node.type === 'paragraph' && nodeChildren?.length) {
|
|
207
|
+
const hasImage = nodeChildren.some((child) =>
|
|
185
208
|
IMAGE_NODE_TYPES.includes(child.type || '')
|
|
186
209
|
);
|
|
187
210
|
if (hasImage) return true;
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
// Recursively check children
|
|
191
|
-
if (
|
|
192
|
-
return
|
|
214
|
+
if (nodeChildren?.length) {
|
|
215
|
+
return nodeChildren.some((child) => checkNode(child));
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
return false;
|
|
196
219
|
}
|
|
197
220
|
|
|
198
|
-
return
|
|
221
|
+
return contentChildren.some((node) => checkNode(node));
|
|
199
222
|
}
|
|
@@ -42,6 +42,26 @@ const hasContent = (node: JSONContent): boolean => {
|
|
|
42
42
|
return false;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
function syncEditorContent(editor: Editor, nextContent: JSONContent | null) {
|
|
46
|
+
const migratedContent = migrateInlineImagesToBlock(nextContent);
|
|
47
|
+
const currentContent = editor.getJSON();
|
|
48
|
+
const contentChanged =
|
|
49
|
+
JSON.stringify(currentContent) !== JSON.stringify(migratedContent);
|
|
50
|
+
|
|
51
|
+
if (contentChanged) {
|
|
52
|
+
editor.commands.setContent(
|
|
53
|
+
migratedContent || { type: 'doc', content: [] },
|
|
54
|
+
{
|
|
55
|
+
emitUpdate: false,
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function serializeEditorContent(content: JSONContent | null) {
|
|
62
|
+
return JSON.stringify(content ?? { type: 'doc', content: [] });
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
interface RichTextEditorProps {
|
|
46
66
|
content: JSONContent | null;
|
|
47
67
|
onChange?: (content: JSONContent | null) => void;
|
|
@@ -127,6 +147,9 @@ export function RichTextEditor({
|
|
|
127
147
|
const debouncedOnChangeRef = useRef<ReturnType<typeof debounce> | null>(null);
|
|
128
148
|
// Track when we're in a programmatic update to skip content sync
|
|
129
149
|
const isProgrammaticUpdateRef = useRef(false);
|
|
150
|
+
const hasDeferredExternalContentRef = useRef(false);
|
|
151
|
+
const deferredExternalContentRef = useRef<JSONContent | null>(null);
|
|
152
|
+
const deferredEditorSnapshotRef = useRef<string | null>(null);
|
|
130
153
|
const getDelegatedImageUpload = useCallback(
|
|
131
154
|
() => onImageUploadRef.current,
|
|
132
155
|
[]
|
|
@@ -566,26 +589,58 @@ export function RichTextEditor({
|
|
|
566
589
|
// This prevents the effect from reverting editor content before state update propagates
|
|
567
590
|
if (isProgrammaticUpdateRef.current) {
|
|
568
591
|
isProgrammaticUpdateRef.current = false;
|
|
592
|
+
hasDeferredExternalContentRef.current = false;
|
|
593
|
+
deferredExternalContentRef.current = null;
|
|
594
|
+
deferredEditorSnapshotRef.current = null;
|
|
569
595
|
return;
|
|
570
596
|
}
|
|
571
597
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
migratedContent || { type: 'doc', content: [] },
|
|
582
|
-
{
|
|
583
|
-
emitUpdate: false,
|
|
584
|
-
}
|
|
585
|
-
);
|
|
598
|
+
if (editor.isFocused) {
|
|
599
|
+
if (!hasDeferredExternalContentRef.current) {
|
|
600
|
+
deferredEditorSnapshotRef.current = serializeEditorContent(
|
|
601
|
+
editor.getJSON()
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
hasDeferredExternalContentRef.current = true;
|
|
605
|
+
deferredExternalContentRef.current = content;
|
|
606
|
+
return;
|
|
586
607
|
}
|
|
608
|
+
|
|
609
|
+
hasDeferredExternalContentRef.current = false;
|
|
610
|
+
deferredExternalContentRef.current = null;
|
|
611
|
+
deferredEditorSnapshotRef.current = null;
|
|
612
|
+
syncEditorContent(editor, content);
|
|
587
613
|
}, [editor, content, allowCollaboration]);
|
|
588
614
|
|
|
615
|
+
useEffect(() => {
|
|
616
|
+
if (!editor || allowCollaboration) return;
|
|
617
|
+
|
|
618
|
+
const syncDeferredContent = () => {
|
|
619
|
+
if (!hasDeferredExternalContentRef.current) return;
|
|
620
|
+
|
|
621
|
+
hasDeferredExternalContentRef.current = false;
|
|
622
|
+
const deferredContent = deferredExternalContentRef.current;
|
|
623
|
+
const deferredEditorSnapshot = deferredEditorSnapshotRef.current;
|
|
624
|
+
deferredExternalContentRef.current = null;
|
|
625
|
+
deferredEditorSnapshotRef.current = null;
|
|
626
|
+
|
|
627
|
+
if (
|
|
628
|
+
deferredEditorSnapshot !== null &&
|
|
629
|
+
serializeEditorContent(editor.getJSON()) !== deferredEditorSnapshot
|
|
630
|
+
) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
syncEditorContent(editor, deferredContent);
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
editor.on('blur', syncDeferredContent);
|
|
638
|
+
|
|
639
|
+
return () => {
|
|
640
|
+
editor.off('blur', syncDeferredContent);
|
|
641
|
+
};
|
|
642
|
+
}, [editor, allowCollaboration]);
|
|
643
|
+
|
|
589
644
|
// Handle initial cursor positioning when focusing from title
|
|
590
645
|
useEffect(() => {
|
|
591
646
|
if (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InputRule } from '@tiptap/core';
|
|
2
2
|
import Collaboration from '@tiptap/extension-collaboration';
|
|
3
3
|
import CollaborationCaret from '@tiptap/extension-collaboration-caret';
|
|
4
|
-
import
|
|
4
|
+
import { Color } from '@tiptap/extension-color';
|
|
5
5
|
import HorizontalRule from '@tiptap/extension-horizontal-rule';
|
|
6
6
|
import Link from '@tiptap/extension-link';
|
|
7
7
|
import { TaskList } from '@tiptap/extension-list';
|
|
@@ -14,11 +14,14 @@ import TableCell from '@tiptap/extension-table-cell';
|
|
|
14
14
|
import TableHeader from '@tiptap/extension-table-header';
|
|
15
15
|
import TableRow from '@tiptap/extension-table-row';
|
|
16
16
|
import TextAlign from '@tiptap/extension-text-align';
|
|
17
|
+
import { TextStyle } from '@tiptap/extension-text-style';
|
|
17
18
|
import Youtube from '@tiptap/extension-youtube';
|
|
18
19
|
import type { Extensions } from '@tiptap/react';
|
|
19
20
|
import StarterKit from '@tiptap/starter-kit';
|
|
20
21
|
import type SupabaseProvider from '@tuturuuu/ui/hooks/supabase-provider';
|
|
21
22
|
import type * as Y from 'yjs';
|
|
23
|
+
import { BackgroundColor } from './background-color-extension';
|
|
24
|
+
import { ThemeAwareHighlight } from './highlight-extension';
|
|
22
25
|
import { CustomImage } from './image-extension';
|
|
23
26
|
import { ListConverter } from './list-converter-extension';
|
|
24
27
|
import { ListItemBase } from './list-item-extension';
|
|
@@ -148,7 +151,10 @@ export function getEditorExtensions({
|
|
|
148
151
|
}),
|
|
149
152
|
TextShortcuts,
|
|
150
153
|
MarkdownPaste,
|
|
151
|
-
|
|
154
|
+
TextStyle,
|
|
155
|
+
Color,
|
|
156
|
+
BackgroundColor,
|
|
157
|
+
ThemeAwareHighlight,
|
|
152
158
|
Link.configure({
|
|
153
159
|
openOnClick: true,
|
|
154
160
|
autolink: true,
|
|
@@ -213,7 +219,7 @@ export function getEditorExtensions({
|
|
|
213
219
|
getOnImageUpload,
|
|
214
220
|
getOnVideoUpload,
|
|
215
221
|
}),
|
|
216
|
-
Video({ onVideoUpload }).configure({
|
|
222
|
+
Video({ onVideoUpload, getOnVideoUpload }).configure({
|
|
217
223
|
HTMLAttributes: {
|
|
218
224
|
class: 'rounded-md my-4',
|
|
219
225
|
},
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
2
|
+
|
|
3
|
+
export const ThemeAwareHighlight = Highlight.extend({
|
|
4
|
+
addAttributes() {
|
|
5
|
+
return {
|
|
6
|
+
...(this.parent?.() ?? {}),
|
|
7
|
+
textColor: {
|
|
8
|
+
default: null,
|
|
9
|
+
parseHTML: (element) => element.style.getPropertyValue('color') || null,
|
|
10
|
+
renderHTML: (attributes) => {
|
|
11
|
+
if (!attributes.textColor) return {};
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
style: `color: ${attributes.textColor}`,
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
}).configure({
|
|
21
|
+
multicolor: true,
|
|
22
|
+
});
|
|
@@ -66,13 +66,31 @@ function clearImageResizeUIFromNodeDom(nodeDom: Node | null): void {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
export type ImageSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
70
|
+
|
|
71
|
+
type UploadHandler = (file: File) => Promise<string>;
|
|
72
|
+
type UploadHandlerGetter = () => UploadHandler | undefined;
|
|
73
|
+
|
|
74
|
+
function resolveUploadHandler({
|
|
75
|
+
configuredHandler,
|
|
76
|
+
delegatedGetter,
|
|
77
|
+
}: {
|
|
78
|
+
configuredHandler?: UploadHandler;
|
|
79
|
+
delegatedGetter?: UploadHandlerGetter;
|
|
80
|
+
}): UploadHandler | undefined {
|
|
81
|
+
if (delegatedGetter) {
|
|
82
|
+
return delegatedGetter();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return configuredHandler;
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
export const __imageExtensionPrivate = {
|
|
70
89
|
clearImageResizeUIFromNodeDom,
|
|
71
90
|
getSelectedImagePos,
|
|
91
|
+
resolveUploadHandler,
|
|
72
92
|
};
|
|
73
93
|
|
|
74
|
-
export type ImageSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
75
|
-
|
|
76
94
|
// Size presets in pixels (will be calculated based on editor width)
|
|
77
95
|
const SIZE_PERCENTAGES: Record<ImageSize, number> = {
|
|
78
96
|
xs: 25, // 25% of editor width
|
|
@@ -137,10 +155,10 @@ function calculatePresetWidth(
|
|
|
137
155
|
}
|
|
138
156
|
|
|
139
157
|
interface ImageOptions {
|
|
140
|
-
onImageUpload?:
|
|
141
|
-
onVideoUpload?:
|
|
142
|
-
getOnImageUpload?:
|
|
143
|
-
getOnVideoUpload?:
|
|
158
|
+
onImageUpload?: UploadHandler;
|
|
159
|
+
onVideoUpload?: UploadHandler;
|
|
160
|
+
getOnImageUpload?: UploadHandlerGetter;
|
|
161
|
+
getOnVideoUpload?: UploadHandlerGetter;
|
|
144
162
|
}
|
|
145
163
|
|
|
146
164
|
/**
|
|
@@ -166,10 +184,10 @@ interface ExtendedImageResizeOptions {
|
|
|
166
184
|
HTMLAttributes: Record<string, any>;
|
|
167
185
|
minWidth?: number;
|
|
168
186
|
maxWidth?: number;
|
|
169
|
-
onImageUpload?:
|
|
170
|
-
onVideoUpload?:
|
|
171
|
-
getOnImageUpload?:
|
|
172
|
-
getOnVideoUpload?:
|
|
187
|
+
onImageUpload?: UploadHandler;
|
|
188
|
+
onVideoUpload?: UploadHandler;
|
|
189
|
+
getOnImageUpload?: UploadHandlerGetter;
|
|
190
|
+
getOnVideoUpload?: UploadHandlerGetter;
|
|
173
191
|
}
|
|
174
192
|
|
|
175
193
|
export const CustomImage = (options: ImageOptions = {}) => {
|
|
@@ -229,15 +247,19 @@ export const CustomImage = (options: ImageOptions = {}) => {
|
|
|
229
247
|
addProseMirrorPlugins() {
|
|
230
248
|
const parentPlugins = this.parent?.() || [];
|
|
231
249
|
const getOnImageUpload = () =>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
250
|
+
resolveUploadHandler({
|
|
251
|
+
delegatedGetter:
|
|
252
|
+
this?.options?.getOnImageUpload ?? options.getOnImageUpload,
|
|
253
|
+
configuredHandler:
|
|
254
|
+
this?.options?.onImageUpload ?? options.onImageUpload,
|
|
255
|
+
});
|
|
236
256
|
const getOnVideoUpload = () =>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
257
|
+
resolveUploadHandler({
|
|
258
|
+
delegatedGetter:
|
|
259
|
+
this?.options?.getOnVideoUpload ?? options.getOnVideoUpload,
|
|
260
|
+
configuredHandler:
|
|
261
|
+
this?.options?.onVideoUpload ?? options.onVideoUpload,
|
|
262
|
+
});
|
|
241
263
|
|
|
242
264
|
return [
|
|
243
265
|
...parentPlugins,
|