frappe-ui 1.0.0-beta.2 → 1.0.0-beta.21
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/experimental/FloatingWindow/FloatingWindow.cy.ts +328 -0
- package/experimental/FloatingWindow/FloatingWindow.vue +303 -0
- package/experimental/FloatingWindow/index.ts +8 -0
- package/experimental/FloatingWindow/types.ts +31 -0
- package/experimental/FloatingWindow/useFloatingWindow.ts +236 -0
- package/experimental/MultiEmailInput/MultiEmailInput.cy.ts +155 -0
- package/experimental/MultiEmailInput/MultiEmailInput.vue +472 -0
- package/experimental/MultiEmailInput/index.ts +17 -0
- package/experimental/MultiEmailInput/stories/AsyncSuggestions.vue +54 -0
- package/experimental/MultiEmailInput/stories/Basic.vue +18 -0
- package/experimental/MultiEmailInput/stories/CustomChip.vue +39 -0
- package/experimental/MultiEmailInput/stories/Labeling.vue +28 -0
- package/experimental/MultiEmailInput/types.ts +138 -0
- package/experimental/MultiEmailInput/utils.ts +60 -0
- package/experimental.ts +12 -0
- package/frappe/Billing/SignupBanner.vue +1 -1
- package/frappe/Billing/TrialBanner.vue +1 -1
- package/frappe/DataImport/DataImport.vue +1 -1
- package/frappe/DataImport/DataImportList.vue +1 -1
- package/frappe/DataImport/ImportSteps.vue +6 -6
- package/frappe/DataImport/MappingStep.vue +1 -1
- package/frappe/DataImport/PreviewStep.vue +9 -9
- package/frappe/DataImport/TemplateModal.vue +1 -1
- package/frappe/DataImport/UploadStep.vue +5 -5
- package/frappe/Filter/Filter.vue +1 -1
- package/frappe/Help/HelpModal.vue +1 -1
- package/frappe/Link/stories/Suffix.vue +2 -2
- package/frappe/Onboarding/GettingStartedBanner.vue +1 -1
- package/frappe/drive/components/AppSwitcher.vue +1 -1
- package/frappe/drive/components/InfoDialog.vue +3 -3
- package/frappe/drive/components/MoveDialog.vue +2 -2
- package/frappe/drive/components/Select/Select.vue +2 -2
- package/frappe/drive/components/ShareDialog.vue +1 -1
- package/frappe/telemetry/index.ts +88 -14
- package/frappe/telemetry/pulse.ts +78 -164
- package/frappe/telemetry/telemetry.test.ts +13 -79
- package/icons/IconPicker.vue +1 -1
- package/package.json +104 -34
- package/src/components/Alert/Alert.md +4 -0
- package/src/components/Alert/Alert.vue +4 -4
- package/src/components/Autocomplete/Autocomplete.vue +6 -6
- package/src/components/Avatar/Avatar.md +4 -0
- package/src/components/Avatar/Avatar.vue +23 -4
- package/src/components/Badge/Badge.api.md +1 -1
- package/src/components/Badge/Badge.cy.ts +34 -16
- package/src/components/Badge/Badge.md +20 -4
- package/src/components/Badge/Badge.vue +60 -45
- package/src/components/Badge/stories/CallMeta.vue +35 -0
- package/src/components/Badge/stories/EventStatus.vue +16 -0
- package/src/components/Badge/stories/ListStatus.vue +91 -0
- package/src/components/Badge/stories/Reactions.vue +33 -0
- package/src/components/Badge/stories/RowTags.vue +39 -0
- package/src/components/Badge/types.ts +1 -2
- package/src/components/BottomSheet/BottomSheet.api.md +57 -0
- package/src/components/BottomSheet/BottomSheet.vue +232 -0
- package/src/components/BottomSheet/index.ts +6 -0
- package/src/components/BottomSheet/types.ts +23 -0
- package/src/components/Breadcrumbs/Breadcrumbs.md +4 -0
- package/src/components/Breadcrumbs/Breadcrumbs.vue +3 -3
- package/src/components/Button/Button.api.md +37 -32
- package/src/components/Button/Button.cy.ts +18 -2
- package/src/components/Button/Button.md +20 -8
- package/src/components/Button/Button.vue +304 -284
- package/src/components/Button/stories/InlineActions.vue +10 -0
- package/src/components/Button/stories/LiveClassCard.vue +34 -0
- package/src/components/Button/stories/SectionAction.vue +17 -0
- package/src/components/Button/stories/SectionControls.vue +28 -0
- package/src/components/Button/stories/SelectionToolbar.vue +20 -0
- package/src/components/Button/stories/StackedActions.vue +10 -0
- package/src/components/Button/types.ts +38 -32
- package/src/components/Calendar/Calendar.api.md +16 -16
- package/src/components/Calendar/Calendar.vue +100 -73
- package/src/components/Calendar/CalendarDaily.vue +21 -20
- package/src/components/Calendar/CalendarMonthEvent.vue +30 -9
- package/src/components/Calendar/CalendarMonthly.vue +60 -39
- package/src/components/Calendar/CalendarTimeMarker.vue +9 -8
- package/src/components/Calendar/CalendarWeekDayEvent.vue +98 -42
- package/src/components/Calendar/CalendarWeekly.vue +77 -41
- package/src/components/Calendar/EventModalContent.vue +32 -16
- package/src/components/Calendar/NewEventModal.vue +19 -16
- package/src/components/Calendar/ShowMoreCalendarEvent.vue +11 -17
- package/src/components/Calendar/{calendarUtils.js → calendarUtils.ts} +76 -52
- package/src/components/Calendar/composables/{useCalendarData.js → useCalendarData.ts} +23 -11
- package/src/components/Calendar/composables/{useEventModal.js → useEventModal.ts} +16 -12
- package/src/components/Calendar/index.ts +12 -2
- package/src/components/Calendar/stories/CustomHeader.vue +1 -1
- package/src/components/Calendar/types.ts +106 -0
- package/src/components/Calendar/{useEventBase.js → useEventBase.ts} +40 -26
- package/src/components/Card.vue +2 -2
- package/src/components/Charts/ECharts.vue +1 -1
- package/src/components/Charts/NumberChart.vue +7 -7
- package/src/components/Charts/eChartOptions.ts +28 -13
- package/src/components/Checkbox/Checkbox.api.md +6 -0
- package/src/components/Checkbox/Checkbox.cy.ts +10 -2
- package/src/components/Checkbox/Checkbox.md +28 -0
- package/src/components/Checkbox/Checkbox.vue +9 -22
- package/src/components/Checkbox/stories/Group.vue +27 -0
- package/src/components/Checkbox/stories/HorizontalGroup.vue +24 -0
- package/src/components/Checkbox/stories/SettingRow.vue +23 -0
- package/src/components/Checkbox/stories/WithDescription.vue +27 -0
- package/src/components/Checkbox/types.ts +7 -0
- package/src/components/CodeEditor/CodeEditor.api.md +139 -0
- package/src/components/CodeEditor/CodeEditor.cy.ts +160 -0
- package/src/components/CodeEditor/CodeEditor.md +104 -0
- package/src/components/CodeEditor/CodeEditor.vue +556 -0
- package/src/components/CodeEditor/CodePreview.test.ts +49 -0
- package/src/components/CodeEditor/CodePreview.vue +45 -0
- package/src/components/CodeEditor/index.ts +17 -0
- package/src/components/CodeEditor/languages.ts +63 -0
- package/src/components/CodeEditor/stories/Default.vue +16 -0
- package/src/components/CodeEditor/stories/Labeling.vue +25 -0
- package/src/components/CodeEditor/stories/MaxHeight.vue +24 -0
- package/src/components/CodeEditor/stories/Preview.vue +36 -0
- package/src/components/CodeEditor/stories/Sizes.vue +15 -0
- package/src/components/CodeEditor/stories/Variants.vue +13 -0
- package/src/components/CodeEditor/theme.ts +272 -0
- package/src/components/CodeEditor/types.ts +69 -0
- package/src/components/Combobox/Combobox.api.md +19 -20
- package/src/components/Combobox/Combobox.cy.ts +54 -14
- package/src/components/Combobox/Combobox.md +4 -0
- package/src/components/Combobox/Combobox.vue +233 -247
- package/src/components/Combobox/ComboboxResults.vue +1 -1
- package/src/components/Combobox/stories/Clearable.vue +2 -2
- package/src/components/Combobox/stories/CreateNew.vue +1 -1
- package/src/components/Combobox/stories/Footer.vue +10 -2
- package/src/components/Combobox/stories/InDialog.vue +1 -1
- package/src/components/Combobox/types.ts +8 -29
- package/src/components/CommandPalette/CommandPaletteItem.vue +1 -1
- package/src/components/ContextMenu/ContextMenu.api.md +52 -0
- package/src/components/ContextMenu/ContextMenu.cy.ts +191 -0
- package/src/components/ContextMenu/ContextMenu.md +25 -0
- package/src/components/ContextMenu/ContextMenu.vue +106 -0
- package/src/components/ContextMenu/index.ts +2 -0
- package/src/components/ContextMenu/stories/FileList.vue +105 -0
- package/src/components/ContextMenu/stories/Groups.vue +95 -0
- package/src/components/ContextMenu/stories/Simple.vue +72 -0
- package/src/components/ContextMenu/types.ts +39 -0
- package/src/components/DatePicker/CalendarPanel.vue +128 -86
- package/src/components/DatePicker/DatePicker.vue +3 -9
- package/src/components/DatePicker/DateRangePicker.vue +4 -8
- package/src/components/DatePicker/DateTimePicker.cy.ts +4 -2
- package/src/components/DatePicker/DateTimePicker.vue +3 -9
- package/src/components/DatePicker/composables.ts +15 -37
- package/src/components/DatePicker/stories/Range.vue +1 -1
- package/src/components/DatePicker/types.ts +1 -1
- package/src/components/DesktopShell/DesktopShell.api.md +40 -0
- package/src/components/DesktopShell/DesktopShell.cy.ts +39 -0
- package/src/components/DesktopShell/DesktopShell.md +31 -0
- package/src/components/DesktopShell/DesktopShell.vue +81 -0
- package/src/components/DesktopShell/index.ts +3 -0
- package/src/components/DesktopShell/stories/Default.vue +97 -0
- package/src/components/DesktopShell/types.ts +2 -0
- package/src/components/Dialog/Dialog.api.md +7 -8
- package/src/components/Dialog/Dialog.md +4 -0
- package/src/components/Dialog/Dialog.vue +8 -7
- package/src/components/Dialog/stories/CommandPalette.vue +1 -1
- package/src/components/Dialog/stories/Wizard.vue +1 -1
- package/src/components/Dialogs.vue +11 -9
- package/src/components/Divider/Divider.api.md +1 -1
- package/src/components/Divider/Divider.md +4 -0
- package/src/components/Dropdown/Dropdown.api.md +10 -35
- package/src/components/Dropdown/Dropdown.md +10 -6
- package/src/components/Dropdown/Dropdown.vue +33 -47
- package/src/components/Dropdown/stories/{05_KebabMenu.vue → KebabMenu.vue} +3 -1
- package/src/components/Dropdown/stories/{06_UserMenu.vue → UserMenu.vue} +1 -1
- package/src/components/Dropdown/types.ts +25 -189
- package/src/components/Duration/Duration.api.md +92 -0
- package/src/components/Duration/Duration.cy.ts +183 -0
- package/src/components/Duration/Duration.md +98 -0
- package/src/components/Duration/Duration.vue +114 -0
- package/src/components/Duration/duration.ts +185 -0
- package/src/components/Duration/index.ts +8 -0
- package/src/components/Duration/stories/Default.vue +10 -0
- package/src/components/Duration/stories/Formats.vue +16 -0
- package/src/components/Duration/stories/RealWorld.vue +50 -0
- package/src/components/Duration/stories/Sizes.vue +15 -0
- package/src/components/Duration/stories/States.vue +21 -0
- package/src/components/Duration/types.ts +52 -0
- package/src/components/ErrorMessage/ErrorMessage.cy.ts +1 -1
- package/src/components/ErrorMessage/ErrorMessage.md +4 -0
- package/src/components/ErrorMessage/ErrorMessage.vue +1 -1
- package/src/components/FileUploader/FileUploader.api.md +36 -11
- package/src/components/FileUploader/FileUploader.cy.ts +66 -0
- package/src/components/FileUploader/FileUploader.md +4 -1
- package/src/components/FileUploader/FileUploader.vue +147 -106
- package/src/components/FileUploader/index.ts +6 -0
- package/src/components/FileUploader/types.ts +43 -0
- package/src/components/FormControl/FormControl.api.md +3 -4
- package/src/components/FormControl/FormControl.md +4 -0
- package/src/components/FormControl/stories/RealForm.vue +3 -3
- package/src/components/FormLabel.vue +5 -8
- package/src/components/HoverCard/HoverCard.api.md +100 -0
- package/src/components/HoverCard/HoverCard.cy.ts +103 -0
- package/src/components/HoverCard/HoverCard.md +52 -0
- package/src/components/HoverCard/HoverCard.vue +91 -0
- package/src/components/HoverCard/index.ts +2 -0
- package/src/components/HoverCard/stories/Arrow.vue +28 -0
- package/src/components/HoverCard/stories/Delays.vue +16 -0
- package/src/components/HoverCard/stories/Example.vue +28 -0
- package/src/components/HoverCard/types.ts +47 -0
- package/src/components/Icon/Icon.cy.ts +78 -0
- package/src/components/Icon/Icon.vue +50 -0
- package/src/components/Icon/index.ts +1 -0
- package/src/components/InputLabeling/InputDescription.vue +1 -1
- package/src/components/InputLabeling/InputError.vue +1 -1
- package/src/components/InputLabeling/InputLabel.vue +21 -7
- package/src/components/InputLabeling/RequiredIndicator.vue +1 -1
- package/src/components/ItemListRow/ItemListRow.vue +1 -1
- package/src/components/ItemListRow/stories/RowStates.vue +1 -1
- package/src/components/KeyboardShortcut.vue +4 -8
- package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.api.md +22 -14
- package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.vue +2 -2
- package/src/components/ListFilter/ListFilter.api.md +5 -6
- package/src/components/ListFilter/ListFilter.vue +161 -57
- package/src/components/ListFilter/NestedPopover.vue +17 -10
- package/src/components/ListFilter/SearchComplete.vue +31 -38
- package/src/components/ListItem.vue +1 -1
- package/src/components/ListView/ListEmptyState.vue +1 -1
- package/src/components/ListView/ListGroupHeader.vue +2 -2
- package/src/components/ListView/ListRow.vue +1 -1
- package/src/components/ListView/ListSelectBanner.vue +1 -1
- package/src/components/ListView/ListView.api.md +322 -6
- package/src/components/ListView/index.ts +25 -0
- package/src/components/ListView/stories/CustomList.vue +1 -1
- package/src/components/ListView/stories/GroupedRows.vue +1 -1
- package/src/components/LoadingIndicator.vue +13 -22
- package/src/components/LoadingText.vue +3 -13
- package/src/components/{Dropdown/DropdownMenuList.vue → Menu/Menu.vue} +110 -75
- package/src/components/{Dropdown/DropdownMenuItemContent.vue → Menu/MenuItemContent.vue} +23 -33
- package/src/components/{Dropdown/DropdownRenderContent.vue → Menu/MenuRenderContent.vue} +1 -1
- package/src/components/Menu/types.ts +225 -0
- package/src/components/{Dropdown → Menu}/utils.ts +36 -40
- package/src/components/MobileNav/MobileNav.api.md +70 -0
- package/src/components/MobileNav/MobileNav.cy.ts +89 -0
- package/src/components/MobileNav/MobileNav.md +34 -0
- package/src/components/MobileNav/MobileNav.vue +15 -0
- package/src/components/MobileNav/MobileNavItem.vue +125 -0
- package/src/components/MobileNav/index.ts +4 -0
- package/src/components/MobileNav/stories/Default.vue +40 -0
- package/src/components/MobileNav/types.ts +33 -0
- package/src/components/MobileShell/MobileShell.api.md +23 -0
- package/src/components/MobileShell/MobileShell.cy.ts +25 -0
- package/src/components/MobileShell/MobileShell.md +24 -0
- package/src/components/MobileShell/MobileShell.vue +41 -0
- package/src/components/MobileShell/index.ts +3 -0
- package/src/components/MobileShell/stories/Default.vue +75 -0
- package/src/components/MobileShell/types.ts +7 -0
- package/src/components/MonthPicker/MonthPicker.cy.ts +16 -0
- package/src/components/MonthPicker/MonthPicker.vue +19 -8
- package/src/components/MultiSelect/MultiSelect.api.md +3 -4
- package/src/components/MultiSelect/MultiSelect.md +42 -10
- package/src/components/MultiSelect/MultiSelect.vue +199 -227
- package/src/components/MultiSelect/MultiSelectResults.vue +1 -1
- package/src/components/MultiSelect/stories/Members.vue +1 -1
- package/src/components/MultiSelect/stories/TagsTrigger.vue +3 -3
- package/src/components/MultiSelect/types.ts +3 -3
- package/src/components/PageHeader/PageHeader.api.md +112 -0
- package/src/components/PageHeader/PageHeader.cy.ts +68 -0
- package/src/components/PageHeader/PageHeader.md +25 -0
- package/src/components/PageHeader/PageHeader.vue +13 -0
- package/src/components/PageHeader/PageHeaderBackButton.vue +29 -0
- package/src/components/PageHeader/PageHeaderBase.vue +51 -0
- package/src/components/PageHeader/PageHeaderMobile.vue +68 -0
- package/src/components/PageHeader/PageHeaderMobileTitle.vue +16 -0
- package/src/components/PageHeader/PageHeaderTarget.vue +13 -0
- package/src/components/PageHeader/PageHeaderTitle.vue +12 -0
- package/src/components/PageHeader/index.ts +7 -0
- package/src/components/PageHeader/stories/Default.vue +19 -0
- package/src/components/PageHeader/stories/Mobile.vue +24 -0
- package/src/components/PageHeader/target.ts +19 -0
- package/src/components/PageHeader/types.ts +19 -0
- package/src/components/Password/Password.md +4 -0
- package/src/components/Password/Password.vue +2 -2
- package/src/components/Popover/Popover.api.md +126 -42
- package/src/components/Popover/Popover.cy.ts +425 -41
- package/src/components/Popover/Popover.md +99 -4
- package/src/components/Popover/Popover.vue +288 -215
- package/src/components/Popover/index.ts +8 -1
- package/src/components/Popover/stories/Arrow.vue +16 -0
- package/src/components/Popover/stories/Bare.vue +20 -0
- package/src/components/Popover/stories/Click.vue +7 -4
- package/src/components/Popover/stories/Controlled.vue +22 -0
- package/src/components/Popover/stories/Dismissible.vue +19 -0
- package/src/components/Popover/stories/MatchTriggerWidth.vue +16 -0
- package/src/components/Popover/stories/SideAlign.vue +17 -0
- package/src/components/Popover/types.ts +101 -23
- package/src/components/Progress/Progress.cy.ts +2 -2
- package/src/components/Progress/Progress.md +4 -0
- package/src/components/Progress/Progress.vue +4 -4
- package/src/components/Rail/Rail.api.md +91 -0
- package/src/components/Rail/Rail.cy.ts +84 -0
- package/src/components/Rail/Rail.md +30 -0
- package/src/components/Rail/Rail.vue +16 -0
- package/src/components/Rail/RailItem.vue +134 -0
- package/src/components/Rail/RailItemBadge.vue +64 -0
- package/src/components/Rail/index.ts +2 -0
- package/src/components/Rail/stories/Default.vue +44 -0
- package/src/components/Rail/types.ts +37 -0
- package/src/components/Rating/Rating.md +4 -0
- package/src/components/Rating/Rating.vue +5 -8
- package/src/components/Rating/stories/CustomSlot.vue +2 -2
- package/src/components/ScrollArea/ScrollArea.api.md +59 -0
- package/src/components/ScrollArea/ScrollArea.vue +46 -0
- package/src/components/ScrollArea/ScrollBar.vue +101 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Select/Select.cy.ts +16 -15
- package/src/components/Select/Select.md +4 -0
- package/src/components/Select/Select.vue +217 -203
- package/src/components/Select/stories/Example.vue +4 -3
- package/src/components/Select/stories/States.vue +1 -1
- package/src/components/Select/stories/TriggerSlots.vue +2 -2
- package/src/components/Select/utils.ts +4 -3
- package/src/components/SettingsDialog/SettingsBody.vue +23 -0
- package/src/components/SettingsDialog/SettingsContent.vue +7 -0
- package/src/components/SettingsDialog/SettingsDialog.api.md +273 -0
- package/src/components/SettingsDialog/SettingsDialog.cy.ts +126 -0
- package/src/components/SettingsDialog/SettingsDialog.md +43 -0
- package/src/components/SettingsDialog/SettingsDialog.vue +76 -0
- package/src/components/SettingsDialog/SettingsHeader.vue +36 -0
- package/src/components/SettingsDialog/SettingsNavGroup.vue +20 -0
- package/src/components/SettingsDialog/SettingsNavItem.vue +33 -0
- package/src/components/SettingsDialog/SettingsPanel.cy.ts +77 -0
- package/src/components/SettingsDialog/SettingsPanel.vue +26 -0
- package/src/components/SettingsDialog/SettingsRow.vue +52 -0
- package/src/components/SettingsDialog/SettingsSidebar.vue +16 -0
- package/src/components/SettingsDialog/index.ts +10 -0
- package/src/components/SettingsDialog/stories/Default.vue +99 -0
- package/src/components/SettingsDialog/stories/PanelBasic.vue +52 -0
- package/src/components/SettingsDialog/stories/panels/NotificationsPanel.vue +57 -0
- package/src/components/SettingsDialog/stories/panels/PreferencesPanel.vue +57 -0
- package/src/components/SettingsDialog/stories/panels/ProfilePanel.vue +38 -0
- package/src/components/SettingsDialog/stories/panels/UsersPanel.vue +83 -0
- package/src/components/SettingsDialog/types.ts +21 -0
- package/src/components/Sidebar/Sidebar.api.md +124 -65
- package/src/components/Sidebar/Sidebar.cy.ts +127 -76
- package/src/components/Sidebar/Sidebar.md +47 -3
- package/src/components/Sidebar/Sidebar.vue +64 -56
- package/src/components/Sidebar/SidebarCollapseToggle.vue +23 -0
- package/src/components/Sidebar/SidebarHeader.vue +76 -52
- package/src/components/Sidebar/SidebarItem.vue +159 -95
- package/src/components/Sidebar/SidebarItemIcon.vue +25 -0
- package/src/components/Sidebar/SidebarLabel.vue +34 -0
- package/src/components/Sidebar/SidebarSection.vue +29 -14
- package/src/components/Sidebar/index.ts +13 -2
- package/src/components/Sidebar/stories/Collapsed.vue +40 -0
- package/src/components/Sidebar/stories/Default.vue +153 -0
- package/src/components/Sidebar/stories/Legacy.vue +36 -0
- package/src/components/Sidebar/types.ts +105 -20
- package/src/components/Skeleton/Skeleton.cy.ts +21 -0
- package/src/components/Skeleton/Skeleton.md +21 -0
- package/src/components/Skeleton/Skeleton.vue +6 -0
- package/src/components/Skeleton/index.ts +1 -0
- package/src/components/Skeleton/stories/Card.vue +12 -0
- package/src/components/Skeleton/stories/List.vue +15 -0
- package/src/components/Skeleton/stories/Text.vue +11 -0
- package/src/components/Slider/Slider.api.md +1 -1
- package/src/components/Slider/Slider.cy.ts +29 -0
- package/src/components/Slider/Slider.md +10 -0
- package/src/components/Slider/Slider.vue +37 -9
- package/src/components/Slider/stories/NegativeValues.vue +12 -0
- package/src/components/Slider/types.ts +1 -1
- package/src/components/Spinner/Spinner.api.md +32 -0
- package/src/components/Spinner/Spinner.cy.ts +87 -0
- package/src/components/Spinner/Spinner.md +21 -0
- package/src/components/Spinner/Spinner.vue +168 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Spinner/stories/InContext.vue +49 -0
- package/src/components/Spinner/stories/Sizes.vue +12 -0
- package/src/components/Spinner/stories/Themes.vue +10 -0
- package/src/components/Spinner/stories/Track.vue +12 -0
- package/src/components/Spinner/types.ts +14 -0
- package/src/components/Switch/Switch.md +4 -0
- package/src/components/Switch/Switch.vue +6 -23
- package/src/components/TabButtons/Pill.vue +161 -0
- package/src/components/TabButtons/TabButtons.api.md +52 -2
- package/src/components/TabButtons/TabButtons.cy.ts +18 -5
- package/src/components/TabButtons/TabButtons.md +49 -0
- package/src/components/TabButtons/TabButtons.vue +207 -109
- package/src/components/TabButtons/index.ts +11 -1
- package/src/components/TabButtons/pillTypes.ts +43 -0
- package/src/components/TabButtons/stories/PrefixSuffix.vue +65 -0
- package/src/components/TabButtons/stories/Sizes.vue +26 -0
- package/src/components/TabButtons/stories/Variants.vue +40 -0
- package/src/components/TabButtons/stories/Vertical.vue +64 -0
- package/src/components/TabButtons/types.ts +49 -0
- package/src/components/Tabs/Tabs.md +4 -0
- package/src/components/Tabs/Tabs.vue +1 -1
- package/src/components/TextEditor/TextEditor.cy.ts +1 -1
- package/src/components/TextEditor/TextEditor.md +3 -0
- package/src/components/TextEditor/TextEditor.vue +7 -4
- package/src/components/TextEditor/components/CodeBlockComponent.vue +4 -194
- package/src/components/TextEditor/components/FontColor.vue +1 -1
- package/src/components/TextEditor/components/LinkPopup.vue +1 -1
- package/src/components/TextEditor/components/MediaNodeView.vue +17 -17
- package/src/components/TextEditor/components/MentionList.vue +1 -1
- package/src/components/TextEditor/components/TextEditorBubbleMenu.vue +14 -5
- package/src/components/TextEditor/components/TextEditorFixedMenu.vue +3 -3
- package/src/components/TextEditor/components/{Menu.vue → TextEditorMenu.vue} +3 -3
- package/src/components/TextEditor/extensions/iframe/InsertIframe.vue +2 -2
- package/src/components/TextEditor/extensions/iframe/iframe-extension.ts +62 -19
- package/src/components/TextEditor/extensions/image-group/ImageGroupNodeView.vue +1 -1
- package/src/components/TextEditor/extensions/image-group/ImageGroupUploadDialog.vue +4 -4
- package/src/components/TextEditor/extensions/suggestion/SuggestionList.vue +1 -1
- package/src/components/TextEditor/extensions/toc-node/TocNodeView.vue +1 -1
- package/src/components/TextEditor/hljs-github.css +197 -0
- package/src/components/TextEditor/index.ts +13 -0
- package/src/components/TextInput/TextInput.cy.ts +8 -3
- package/src/components/TextInput/TextInput.md +4 -0
- package/src/components/TextInput/TextInput.vue +11 -15
- package/src/components/TextInput/stories/Slots.vue +1 -1
- package/src/components/Textarea/Textarea.cy.ts +1 -1
- package/src/components/Textarea/Textarea.md +4 -0
- package/src/components/Textarea/Textarea.vue +11 -15
- package/src/components/ThemeSwitcher/ThemePreview.vue +74 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.api.md +82 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.cy.ts +101 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.md +70 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.vue +145 -0
- package/src/components/ThemeSwitcher/index.ts +2 -0
- package/src/components/ThemeSwitcher/previews/PreviewWindow.vue +74 -0
- package/src/components/ThemeSwitcher/stories/Branded.vue +12 -0
- package/src/components/ThemeSwitcher/stories/Default.vue +7 -0
- package/src/components/ThemeSwitcher/stories/Menu.vue +53 -0
- package/src/components/ThemeSwitcher/stories/Toggle.vue +25 -0
- package/src/components/ThemeSwitcher/types.ts +35 -0
- package/src/components/TimePicker/TimePicker.api.md +67 -15
- package/src/components/TimePicker/TimePicker.cy.ts +130 -14
- package/src/components/TimePicker/TimePicker.md +6 -0
- package/src/components/TimePicker/TimePicker.vue +74 -19
- package/src/components/TimePicker/stories/Labeling.vue +21 -0
- package/src/components/TimePicker/stories/Range.vue +1 -1
- package/src/components/TimePicker/stories/SizesAndVariants.vue +23 -0
- package/src/components/TimePicker/stories/TwentyFour.vue +1 -1
- package/src/components/TimePicker/types.ts +15 -3
- package/src/components/TimePicker/utils.ts +82 -21
- package/src/components/Toast/Toast.md +1 -1
- package/src/components/Toast/Toast.sanitize.test.ts +50 -0
- package/src/components/Toast/Toast.test.ts +5 -0
- package/src/components/Toast/Toast.vue +7 -7
- package/src/components/Toast/ToastProvider.vue +18 -9
- package/src/components/Toast/stories/CustomIcon.vue +1 -1
- package/src/components/Toast/toast.ts +32 -8
- package/src/components/Tooltip/Tooltip.api.md +43 -5
- package/src/components/Tooltip/Tooltip.cy.ts +48 -0
- package/src/components/Tooltip/Tooltip.md +10 -0
- package/src/components/Tooltip/Tooltip.vue +20 -5
- package/src/components/Tooltip/TooltipBubble.vue +2 -2
- package/src/components/Tooltip/TooltipProvider.vue +53 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/components/Tooltip/stories/Group.vue +22 -0
- package/src/components/Tooltip/stories/Slots.vue +2 -2
- package/src/components/Tooltip/types.ts +1 -1
- package/src/components/Tree/Tree.api.md +76 -18
- package/src/components/Tree/Tree.md +144 -2
- package/src/components/Tree/Tree.vue +276 -97
- package/src/components/Tree/TreeItem.vue +263 -0
- package/src/components/Tree/index.ts +9 -1
- package/src/components/Tree/stories/DragDrop.vue +94 -0
- package/src/components/Tree/stories/Example.vue +11 -34
- package/src/components/Tree/stories/ExpandAll.vue +48 -0
- package/src/components/Tree/stories/Guides.vue +49 -0
- package/src/components/Tree/stories/ListView.vue +74 -0
- package/src/components/Tree/tree.cy.ts +218 -90
- package/src/components/Tree/types.ts +114 -24
- package/src/components/Tree/useTreeDragDrop.ts +203 -0
- package/src/components/Tree/useTreeKeyboard.ts +109 -0
- package/src/components/VueGridLayout/Layout.vue +1 -1
- package/src/components/shared/picker/PickerShell.vue +20 -15
- package/src/components/shared/popover/PopoverPanel.vue +67 -0
- package/src/components/shared/popover/popoverPanel.css +102 -0
- package/src/components/shared/selection/utils.ts +6 -6
- package/src/composables/useAutofocusOnOpen.ts +2 -0
- package/src/composables/useScreenSize.ts +46 -0
- package/src/composables/useScrollContainer.ts +108 -0
- package/src/composables/useShortcut.ts +3 -0
- package/src/data-fetching/docStore.test.ts +74 -0
- package/src/data-fetching/docStore.ts +18 -7
- package/src/data-fetching/useCall/types.ts +1 -0
- package/src/data-fetching/useCall/useCall.test.ts +43 -0
- package/src/data-fetching/useCall/useCall.ts +17 -4
- package/src/data-fetching/useDoc/useDoc.test.ts +61 -2
- package/src/data-fetching/useDoc/useDoc.ts +35 -15
- package/src/data-fetching/useDoctype/useDoctype.ts +6 -5
- package/src/data-fetching/useFrappeFetch.ts +17 -4
- package/src/data-fetching/useList/types.ts +1 -0
- package/src/data-fetching/useList/useList.test.ts +40 -0
- package/src/data-fetching/useList/useList.ts +20 -13
- package/src/data-fetching/useNewDoc/useNewDoc.ts +9 -5
- package/src/directives/focus.ts +3 -3
- package/src/index.ts +105 -74
- package/src/mocks/handlers.ts +8 -0
- package/src/molecules/editor/Editor.cy.ts +6 -1
- package/src/molecules/editor/EditorBubbleMenu.vue +1 -1
- package/src/molecules/editor/EditorContent.vue +50 -9
- package/src/molecules/editor/EditorFixedMenu.vue +2 -1
- package/src/molecules/editor/EditorFloatingMenu.vue +1 -1
- package/src/molecules/editor/EditorTableMenu.vue +303 -0
- package/src/molecules/editor/MenuItems.vue +81 -69
- package/src/molecules/editor/commands.ts +10 -2
- package/src/molecules/editor/components/AttachmentNodeView.vue +115 -0
- package/src/molecules/editor/components/EditorDropZone.vue +105 -0
- package/src/molecules/editor/components/EditorPopover.vue +89 -0
- package/src/molecules/editor/components/MediaNodeView.vue +156 -49
- package/src/molecules/editor/components/MediaResizeHandles.vue +38 -0
- package/src/molecules/editor/components/MediaToolbar.vue +108 -54
- package/src/molecules/editor/components/TableContextMenu.vue +144 -0
- package/src/molecules/editor/components/UploadProgressIndicator.vue +33 -0
- package/src/molecules/editor/components/VideoControls.vue +208 -0
- package/src/molecules/editor/components/font-color/ColorSwatchGrid.vue +3 -5
- package/src/molecules/editor/components/font-color/fontColorController.ts +28 -23
- package/src/molecules/editor/components/font-color/swatches.ts +1 -1
- package/src/molecules/editor/components/media-node-view-controller.ts +4 -4
- package/src/molecules/editor/components/media-node-view-utils.test.ts +0 -13
- package/src/molecules/editor/components/media-node-view-utils.ts +0 -10
- package/src/molecules/editor/components/table-color/tableCellColorController.ts +80 -0
- package/src/molecules/editor/components/table-size-picker/TableSizePicker.vue +92 -0
- package/src/molecules/editor/components/table-size-picker/tableSizePickerController.ts +40 -0
- package/src/molecules/editor/composables/useEditorFileDrop.ts +117 -0
- package/src/molecules/editor/composables/useFloatingPopup.ts +46 -2
- package/src/molecules/editor/composables/useNodeViewResize.test.ts +60 -11
- package/src/molecules/editor/composables/useNodeViewResize.ts +80 -21
- package/src/molecules/editor/composables/useTableCellColorState.ts +78 -0
- package/src/molecules/editor/composables/useWindowFileDragging.ts +68 -0
- package/src/molecules/editor/extensions/attachment/attachment-engine.ts +250 -0
- package/src/molecules/editor/extensions/attachment/attachment-extension.ts +248 -0
- package/src/molecules/editor/extensions/attachment/attachment.flow.test.ts +80 -0
- package/src/molecules/editor/extensions/attachment/attachment.roundtrip.test.ts +50 -0
- package/src/molecules/editor/extensions/attachment/index.ts +5 -0
- package/src/molecules/editor/extensions/code-block/CodeBlockComponent.css +16 -26
- package/src/molecules/editor/extensions/code-block/CodeBlockComponent.vue +107 -22
- package/src/molecules/editor/extensions/content-paste/content-paste-extension.ts +5 -3
- package/src/molecules/editor/extensions/iframe/IframeInsertDialog.vue +34 -7
- package/src/molecules/editor/extensions/iframe/IframeNodeView.vue +54 -48
- package/src/molecules/editor/extensions/iframe/iframe-allowlist.ts +33 -4
- package/src/molecules/editor/extensions/iframe/iframe-commands.ts +49 -5
- package/src/molecules/editor/extensions/iframe/iframe-embed-utils.ts +30 -0
- package/src/molecules/editor/extensions/iframe/iframe-extension.ts +4 -4
- package/src/molecules/editor/extensions/iframe/iframeInsertDialogController.ts +12 -1
- package/src/molecules/editor/extensions/iframe/index.ts +4 -4
- package/src/molecules/editor/extensions/iframe/useIframeDialog.ts +25 -13
- package/src/molecules/editor/extensions/image/image-extension.ts +29 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupGrid.vue +27 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupGridCell.vue +55 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupNodeView.vue +64 -31
- package/src/molecules/editor/extensions/image-group/ImageGroupUploadDialog.vue +84 -40
- package/src/molecules/editor/extensions/image-group/image-group-extension.ts +4 -3
- package/src/molecules/editor/extensions/image-group/useImageGroupDialog.ts +116 -17
- package/src/molecules/editor/extensions/link/LinkEditorPopup.vue +214 -71
- package/src/molecules/editor/extensions/link/link-click-plugin.ts +10 -15
- package/src/molecules/editor/extensions/link/link-commands.ts +24 -3
- package/src/molecules/editor/extensions/link/link-extension.ts +11 -2
- package/src/molecules/editor/extensions/link/link-popup-controller.test.ts +49 -0
- package/src/molecules/editor/extensions/link/link-popup-controller.ts +67 -2
- package/src/molecules/editor/extensions/link/link-shortcut-plugin.ts +7 -2
- package/src/molecules/editor/extensions/media-drop/media-drop-extension.ts +177 -0
- package/src/molecules/editor/extensions/shared/media-dimensions.ts +36 -3
- package/src/molecules/editor/extensions/shared/media-node-ops.ts +17 -0
- package/src/molecules/editor/extensions/shared/media-plugin.test.ts +60 -0
- package/src/molecules/editor/extensions/shared/media-plugin.ts +7 -37
- package/src/molecules/editor/extensions/shared/media-upload-engine.test.ts +39 -2
- package/src/molecules/editor/extensions/shared/media-upload-engine.ts +104 -10
- package/src/molecules/editor/extensions/shared/media-upload-state.ts +55 -0
- package/src/molecules/editor/extensions/shared/media-upload-types.ts +52 -1
- package/src/molecules/editor/extensions/shared/suggestion-helpers.ts +20 -0
- package/src/molecules/editor/extensions/shared/suggestion-types.ts +7 -0
- package/src/molecules/editor/extensions/shared/upload-types.ts +1 -0
- package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.test.ts +38 -0
- package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.ts +119 -55
- package/src/molecules/editor/extensions/suggestion/SuggestionList.vue +62 -22
- package/src/molecules/editor/extensions/suggestion/SuggestionListItem.vue +1 -1
- package/src/molecules/editor/extensions/suggestion/createSuggestionExtension.ts +4 -0
- package/src/molecules/editor/extensions/table/drag-scroll.ts +154 -0
- package/src/molecules/editor/extensions/table/table-cell-color.ts +188 -0
- package/src/molecules/editor/extensions/table/table-navigation.ts +560 -0
- package/src/molecules/editor/extensions/table/table-selection-overlay.ts +137 -0
- package/src/molecules/editor/extensions/toc-node/TocNodeView.vue +1 -1
- package/src/molecules/editor/extensions/toc-node/toc-render.ts +7 -3
- package/src/molecules/editor/extensions/video/video-extension.ts +72 -5
- package/src/molecules/editor/extensions.ts +176 -8
- package/src/molecules/editor/index.ts +9 -4
- package/src/molecules/editor/kits.test.ts +21 -1
- package/src/molecules/editor/kits.ts +56 -33
- package/src/molecules/editor/menu.test.ts +3 -1
- package/src/molecules/editor/menu.ts +180 -18
- package/src/molecules/editor/stories/Comment.vue +4 -4
- package/src/molecules/editor/stories/Inline.vue +1 -1
- package/src/molecules/editor/stories/Primitives.vue +1 -1
- package/src/molecules/editor/stories/RichText.vue +1 -1
- package/src/molecules/editor/style.css +80 -3
- package/src/molecules/editor/useEditor.test.ts +4 -0
- package/src/molecules/editor/useEditor.ts +7 -3
- package/src/molecules/list/List.cy.ts +626 -0
- package/src/molecules/list/List.vue +93 -0
- package/src/molecules/list/ListCell.vue +15 -0
- package/src/molecules/list/ListGroup.vue +40 -0
- package/src/molecules/list/ListHeader.vue +62 -0
- package/src/molecules/list/ListHeaderCell.vue +26 -0
- package/src/molecules/list/ListHeaderCellSort.vue +106 -0
- package/src/molecules/list/ListRow.vue +55 -0
- package/src/molecules/list/ListRowBase.vue +103 -0
- package/src/molecules/list/ListRows.vue +120 -0
- package/src/molecules/list/index.ts +24 -0
- package/src/molecules/list/list-context.ts +44 -0
- package/src/molecules/list/list.api.md +276 -0
- package/src/molecules/list/list.md +119 -0
- package/src/molecules/list/stories/Columns.vue +103 -0
- package/src/molecules/list/stories/Feed.vue +105 -0
- package/src/molecules/list/stories/Virtual.vue +31 -0
- package/src/molecules/list/style.css +173 -0
- package/src/molecules/list/types.ts +82 -0
- package/src/molecules/list/useVirtualRows.ts +80 -0
- package/src/resources/documentResource.d.ts +28 -0
- package/src/resources/documentResource.js +32 -4
- package/src/resources/documentResource.test.ts +111 -0
- package/src/resources/listResource.d.ts +33 -0
- package/src/resources/plugin.d.ts +7 -0
- package/src/resources/resources.d.ts +35 -0
- package/src/utils/{call.js → call.ts} +50 -9
- package/src/utils/config.ts +16 -1
- package/src/utils/confirmDialog.d.ts +17 -0
- package/src/utils/debounce.ts +6 -7
- package/src/utils/dialog.cy.ts +9 -9
- package/src/utils/fileSize.ts +36 -0
- package/src/utils/fileUploadHandler.ts +46 -6
- package/src/utils/frappeRequest.test.ts +79 -0
- package/src/utils/{frappeRequest.js → frappeRequest.ts} +76 -13
- package/src/utils/pageMeta.ts +1 -1
- package/src/utils/plugin.ts +58 -0
- package/src/utils/{request.js → request.ts} +28 -3
- package/src/utils/{socketio.js → socketio.ts} +11 -1
- package/src/utils/theme.ts +48 -57
- package/src/utils/useFileUpload.ts +80 -23
- package/tailwind/audit-token-drift.cjs +176 -0
- package/tailwind/colorPalette.js +69 -8
- package/tailwind/colors.json +1115 -397
- package/tailwind/figma-tokens-to-theme.js +364 -0
- package/tailwind/generated/colors.json +1217 -0
- package/tailwind/generated/effects.json +41 -0
- package/tailwind/generated/radius.json +20 -0
- package/tailwind/generated/typography.json +479 -0
- package/tailwind/migrate-tokens-v2.js +453 -0
- package/tailwind/migrate-tokens-v2.test.js +55 -0
- package/tailwind/plugin.js +220 -15
- package/tailwind/preset.js +15 -0
- package/tailwind/tokens.js +35 -37
- package/tsconfig.base.json +2 -2
- package/vitepress/components/Docs/CommandPalette.vue +214 -0
- package/vitepress/components/Docs/Demo.vue +60 -0
- package/vitepress/components/Docs/EmitsTable.vue +99 -0
- package/vitepress/components/Docs/MobileNavSheet.vue +148 -0
- package/vitepress/components/Docs/OnThisPage.vue +74 -0
- package/vitepress/components/Docs/PrevNextBtns.vue +58 -0
- package/vitepress/components/Docs/PropsTable.vue +185 -0
- package/vitepress/components/Docs/Search.vue +34 -0
- package/vitepress/components/Docs/Sidebar.vue +91 -0
- package/vitepress/components/Docs/SlotsTable.vue +99 -0
- package/vitepress/components/Docs/index.ts +13 -0
- package/vitepress/components/Docs/sidebarList.ts +33 -0
- package/vitepress/components/Layout.vue +58 -0
- package/vitepress/components/Navbar.vue +126 -0
- package/vitepress/components/index.ts +3 -0
- package/vitepress/composables/index.ts +3 -0
- package/vitepress/composables/useTheme.ts +39 -0
- package/vitepress/css/fonts/Inter-Italic.var.woff2 +0 -0
- package/vitepress/css/fonts/Inter.var.woff2 +0 -0
- package/vitepress/css/style.css +198 -0
- package/vitepress/index.d.ts +23 -0
- package/vitepress/index.node.ts +175 -0
- package/vitepress/index.ts +17 -0
- package/vitepress/plugins/colocatedComponentDocs.ts +164 -0
- package/vitepress/plugins/componentTransformer.ts +286 -0
- package/vitepress/plugins/index.ts +12 -0
- package/vitepress/state.ts +10 -0
- package/vitepress/theme/index.ts +21 -0
- package/frappe/telemetry/utils.ts +0 -15
- package/src/components/Badge/stories/Themes.vue +0 -10
- package/src/components/Badge/stories/Variants.vue +0 -10
- package/src/components/Button/stories/Icons.vue +0 -27
- package/src/components/Button/stories/Sizes.vue +0 -11
- package/src/components/Button/stories/Themes.vue +0 -10
- package/src/components/Button/stories/Variants.vue +0 -10
- package/src/components/Popover/stories/Hover.vue +0 -14
- package/src/components/Resource.vue +0 -21
- package/src/components/Sidebar/stories/Example.vue +0 -76
- package/src/components/Spinner.vue +0 -3
- package/src/components/TabButtons/TabButtons.story.vue +0 -22
- package/src/utils/plugin.js +0 -24
- /package/src/components/Dropdown/stories/{02_Shortcuts.vue → Shortcuts.vue} +0 -0
- /package/src/components/Dropdown/stories/{01_Simple.vue → Simple.vue} +0 -0
- /package/src/components/Dropdown/stories/{03_Submenus.vue → Submenus.vue} +0 -0
- /package/src/components/Dropdown/stories/{04_Switches.vue → Switches.vue} +0 -0
- /package/src/components/{Dropdown/DropdownRenderContentAsChild.vue → Menu/MenuRenderContentAsChild.vue} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Link from '@tiptap/extension-link'
|
|
2
|
-
import { buildOpenLinkEditor } from './link-commands'
|
|
2
|
+
import { buildOpenLinkEditor, type OpenLinkEditorOptions } from './link-commands'
|
|
3
3
|
import { linkPastePlugin } from './link-paste-plugin'
|
|
4
4
|
import { clearLinkOnBoundaryPlugin } from './clear-link-on-boundary-plugin'
|
|
5
5
|
import { linkClickPlugin } from './link-click-plugin'
|
|
@@ -9,7 +9,7 @@ declare module '@tiptap/core' {
|
|
|
9
9
|
interface Commands<ReturnType> {
|
|
10
10
|
linkEditor: {
|
|
11
11
|
/** Opens the link editor bubble menu. */
|
|
12
|
-
openLinkEditor: () => ReturnType
|
|
12
|
+
openLinkEditor: (options?: OpenLinkEditorOptions) => ReturnType
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -33,6 +33,15 @@ export const LinkExtension = Link.extend({
|
|
|
33
33
|
autolink: true,
|
|
34
34
|
defaultProtocol: 'https',
|
|
35
35
|
linkOnPaste: false,
|
|
36
|
+
// Don't auto-link bare dotted identifiers like `data.doc.name` (`.name` is a
|
|
37
|
+
// real TLD, so linkify matches them). Only auto-link inputs that carry an
|
|
38
|
+
// explicit scheme (`http://`, `mailto:`, …) or a `www.` prefix — i.e. things
|
|
39
|
+
// a user actually means as a URL. `url` here is the raw matched text.
|
|
40
|
+
shouldAutoLink: (url: string): boolean => {
|
|
41
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(url)
|
|
42
|
+
const hasWww = /^www\./i.test(url)
|
|
43
|
+
return hasScheme || hasWww
|
|
44
|
+
},
|
|
36
45
|
}
|
|
37
46
|
},
|
|
38
47
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest'
|
|
6
|
+
import { lockScroll } from './link-popup-controller'
|
|
7
|
+
|
|
8
|
+
describe('lockScroll', () => {
|
|
9
|
+
// Regression: opening the link popup over a scrolled editor used to drop the
|
|
10
|
+
// container's `overflow-y` on close, so long content overflowed its box and
|
|
11
|
+
// painted over the toolbar/Submit. The lock must restore the exact prior
|
|
12
|
+
// overflow, even when it was authored as the `overflow-y` longhand.
|
|
13
|
+
it('restores an `overflow-y` longhand after lock/unlock', () => {
|
|
14
|
+
const el = document.createElement('div')
|
|
15
|
+
el.style.overflowY = 'auto'
|
|
16
|
+
|
|
17
|
+
const unlock = lockScroll(el)
|
|
18
|
+
expect(el.style.overflowY).toBe('hidden')
|
|
19
|
+
expect(el.style.overflowX).toBe('hidden')
|
|
20
|
+
|
|
21
|
+
unlock()
|
|
22
|
+
expect(el.style.overflowY).toBe('auto')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('restores each axis independently after lock/unlock', () => {
|
|
26
|
+
const el = document.createElement('div')
|
|
27
|
+
el.style.overflowX = 'hidden'
|
|
28
|
+
el.style.overflowY = 'auto'
|
|
29
|
+
|
|
30
|
+
lockScroll(el)()
|
|
31
|
+
|
|
32
|
+
expect(el.style.overflowX).toBe('hidden')
|
|
33
|
+
expect(el.style.overflowY).toBe('auto')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('leaves no inline overflow when the element had none', () => {
|
|
37
|
+
const el = document.createElement('div')
|
|
38
|
+
|
|
39
|
+
lockScroll(el)()
|
|
40
|
+
|
|
41
|
+
expect(el.style.overflowX).toBe('')
|
|
42
|
+
expect(el.style.overflowY).toBe('')
|
|
43
|
+
expect(el.getAttribute('style')).toBeFalsy()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('is a no-op for a null scroll parent', () => {
|
|
47
|
+
expect(() => lockScroll(null)()).not.toThrow()
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -10,10 +10,17 @@ import LinkEditorPopup from './LinkEditorPopup.vue'
|
|
|
10
10
|
export interface OpenLinkPopupOptions {
|
|
11
11
|
/** Existing href to seed the editor with (`null` when creating a new link). */
|
|
12
12
|
href: string | null
|
|
13
|
+
/** Open straight into edit mode (vs. the read-only view). */
|
|
14
|
+
startInEdit?: boolean
|
|
13
15
|
/** Anchor element used for popup ownership and outside-click containment. */
|
|
14
16
|
anchor: HTMLElement
|
|
15
17
|
/** Optional positioning reference. Defaults to the editor selection. */
|
|
16
18
|
virtualReference?: VirtualReference
|
|
19
|
+
/**
|
|
20
|
+
* Called when the user dismisses the popup with Escape (not on apply or
|
|
21
|
+
* click-outside). Used to return focus to the editor.
|
|
22
|
+
*/
|
|
23
|
+
onEscape?: () => void
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
/**
|
|
@@ -35,6 +42,11 @@ export function openLinkPopup(
|
|
|
35
42
|
): Promise<string | null> {
|
|
36
43
|
const { href, anchor } = options
|
|
37
44
|
|
|
45
|
+
// Lock the page's scroll container while the popup is open so the anchored
|
|
46
|
+
// editor can't scroll away underneath it. The app scrolls an inner
|
|
47
|
+
// overflow-auto element (not <body>), so we lock the nearest such ancestor.
|
|
48
|
+
const unlockScroll = lockScroll(getScrollParent(anchor))
|
|
49
|
+
|
|
38
50
|
return new Promise<string | null>((resolve) => {
|
|
39
51
|
let settled = false
|
|
40
52
|
const settle = (value: string | null) => {
|
|
@@ -47,16 +59,21 @@ export function openLinkPopup(
|
|
|
47
59
|
anchor,
|
|
48
60
|
component: LinkEditorPopup,
|
|
49
61
|
closeOnAnchorPointerDown: true,
|
|
62
|
+
// The popup owns Escape: in view mode it closes; in edit mode the first
|
|
63
|
+
// Escape steps back to view mode and only a second one closes.
|
|
64
|
+
closeOnEscape: false,
|
|
50
65
|
virtualReference: options.virtualReference ?? {
|
|
51
66
|
getBoundingClientRect: () => selectionRect(anchor),
|
|
52
67
|
},
|
|
53
68
|
floatingOptions: { placement: 'top' },
|
|
54
69
|
props: {
|
|
55
70
|
href: href ?? '',
|
|
71
|
+
startInEdit: options.startInEdit ?? !href,
|
|
56
72
|
onClose: () => {
|
|
57
|
-
// Cancel: resolve null, then
|
|
73
|
+
// Cancel via Escape: resolve null, tear down, then hand focus back.
|
|
58
74
|
settle(null)
|
|
59
75
|
popup.destroy()
|
|
76
|
+
options.onEscape?.()
|
|
60
77
|
},
|
|
61
78
|
onUpdateHref: (newHref: string) => {
|
|
62
79
|
settle(newHref)
|
|
@@ -69,14 +86,62 @@ export function openLinkPopup(
|
|
|
69
86
|
// else settled the promise by the time the popup is gone.
|
|
70
87
|
const originalDestroy = popup.destroy
|
|
71
88
|
popup.destroy = () => {
|
|
89
|
+
unlockScroll()
|
|
72
90
|
settle(null)
|
|
73
91
|
originalDestroy()
|
|
74
92
|
}
|
|
75
93
|
|
|
76
|
-
if (!popup.floating)
|
|
94
|
+
if (!popup.floating) {
|
|
95
|
+
unlockScroll()
|
|
96
|
+
settle(null)
|
|
97
|
+
}
|
|
77
98
|
})
|
|
78
99
|
}
|
|
79
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Lock vertical+horizontal scrolling on `el` and return a function that
|
|
103
|
+
* restores it.
|
|
104
|
+
*
|
|
105
|
+
* Save/restore the `overflow-x`/`overflow-y` *longhands* — never the `overflow`
|
|
106
|
+
* shorthand. A scroll container's value usually lives in a longhand (e.g.
|
|
107
|
+
* EditorContent sets `overflow-y: auto` inline); reading `.style.overflow`
|
|
108
|
+
* returns '' whenever the two axes differ, so locking via the shorthand and
|
|
109
|
+
* later clearing it would drop the original `overflow-y` and leave the editor
|
|
110
|
+
* overflowing its box — content then paints over the toolbar (the link-edit
|
|
111
|
+
* overflow bug). The longhand getters reflect the value regardless of how it
|
|
112
|
+
* was originally authored, so restoring them reproduces the prior state exactly.
|
|
113
|
+
*
|
|
114
|
+
* No-op (returns a no-op restore) when `el` is `null`.
|
|
115
|
+
*/
|
|
116
|
+
export function lockScroll(el: HTMLElement | null): () => void {
|
|
117
|
+
if (!el) return () => {}
|
|
118
|
+
const previous = { x: el.style.overflowX, y: el.style.overflowY }
|
|
119
|
+
el.style.overflowX = 'hidden'
|
|
120
|
+
el.style.overflowY = 'hidden'
|
|
121
|
+
return () => {
|
|
122
|
+
el.style.overflowX = previous.x
|
|
123
|
+
el.style.overflowY = previous.y
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Nearest scrollable ancestor of `el` (the element that actually scrolls), used
|
|
129
|
+
* to lock scrolling while editing. The app's scroll container is an inner
|
|
130
|
+
* `overflow-auto` div, not `document.body`, so locking the body is a no-op —
|
|
131
|
+
* we must lock this element instead. Returns `null` if nothing scrolls.
|
|
132
|
+
*/
|
|
133
|
+
function getScrollParent(el: HTMLElement | null): HTMLElement | null {
|
|
134
|
+
let node: HTMLElement | null = el
|
|
135
|
+
while (node && node !== document.body) {
|
|
136
|
+
const { overflowY } = getComputedStyle(node)
|
|
137
|
+
if (/(auto|scroll|overlay)/.test(overflowY) && node.scrollHeight > node.clientHeight) {
|
|
138
|
+
return node
|
|
139
|
+
}
|
|
140
|
+
node = node.parentElement
|
|
141
|
+
}
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
|
|
80
145
|
/**
|
|
81
146
|
* Build a live `DOMRect` from the current selection (collapsed → caret rect,
|
|
82
147
|
* expanded → selection rect). Falls back to the anchor's own rect when there
|
|
@@ -24,7 +24,7 @@ export function linkShortcutPlugin(options: LinkShortcutPluginOptions): Plugin {
|
|
|
24
24
|
return new Plugin({
|
|
25
25
|
key: new PluginKey('linkShortcut'),
|
|
26
26
|
props: {
|
|
27
|
-
handleKeyDown: (
|
|
27
|
+
handleKeyDown: (view, event): boolean => {
|
|
28
28
|
const isModK =
|
|
29
29
|
(event.metaKey || event.ctrlKey) &&
|
|
30
30
|
!event.shiftKey &&
|
|
@@ -34,7 +34,12 @@ export function linkShortcutPlugin(options: LinkShortcutPluginOptions): Plugin {
|
|
|
34
34
|
|
|
35
35
|
event.preventDefault()
|
|
36
36
|
event.stopPropagation()
|
|
37
|
-
|
|
37
|
+
// An explicit selection (e.g. selecting a link) → edit its URL straight
|
|
38
|
+
// away; a bare cursor in a link → the default read-only view.
|
|
39
|
+
const hasSelection = !view.state.selection.empty
|
|
40
|
+
editor.commands.openLinkEditor(
|
|
41
|
+
hasSelection ? { startInEdit: true } : undefined,
|
|
42
|
+
)
|
|
38
43
|
return true
|
|
39
44
|
},
|
|
40
45
|
},
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core'
|
|
2
|
+
import { Plugin, Selection } from '@tiptap/pm/state'
|
|
3
|
+
import type { EditorView } from '@tiptap/pm/view'
|
|
4
|
+
import { openImageGroupUploadDialog } from '#molecules/editor/extensions/image-group/imageGroupDialogController'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The single drop pipeline.
|
|
8
|
+
*
|
|
9
|
+
* One place decides what a set of dropped files becomes, so every entry point
|
|
10
|
+
* behaves identically: the precise in-prose drop (this extension's ProseMirror
|
|
11
|
+
* plugin, which drops at the cursor) and the generous editor-area drop zone
|
|
12
|
+
* (`EditorDropZone`, which calls `dropFiles` directly).
|
|
13
|
+
*
|
|
14
|
+
* Routing by type/count:
|
|
15
|
+
* - exactly one image -> insert it inline (`uploadImage`)
|
|
16
|
+
* - several images -> open the image group dialog (matches the toolbar's
|
|
17
|
+
* multi-image pick, instead of stacking them inline)
|
|
18
|
+
* - any video(s) -> upload inline (`uploadVideoFiles`)
|
|
19
|
+
* - anything else -> upload inline as attachments (`uploadAttachmentFiles`)
|
|
20
|
+
*
|
|
21
|
+
* Type routing is feature-detected via the editor's commands, so a kit without
|
|
22
|
+
* the image, video, or attachment extension simply skips that branch.
|
|
23
|
+
*
|
|
24
|
+
* Drop is handled under `handleDOMEvents.drop`; a file paste of non-image,
|
|
25
|
+
* non-video files is handled under `props.handlePaste` (image/video pastes keep
|
|
26
|
+
* flowing to their own extensions' paste handlers).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const IMAGE_RE = /^image\//i
|
|
30
|
+
const VIDEO_RE = /^video\//i
|
|
31
|
+
|
|
32
|
+
/** Pull every `File` out of a drag `DataTransfer`, regardless of MIME. */
|
|
33
|
+
function collectFiles(dataTransfer: DataTransfer | null | undefined): File[] {
|
|
34
|
+
const items = dataTransfer?.items
|
|
35
|
+
if (!items || items.length === 0) return []
|
|
36
|
+
const files: File[] = []
|
|
37
|
+
for (let i = 0; i < items.length; i++) {
|
|
38
|
+
if (items[i].kind === 'file') {
|
|
39
|
+
const file = items[i].getAsFile()
|
|
40
|
+
if (file) files.push(file)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return files
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare module '@tiptap/core' {
|
|
47
|
+
interface Commands<ReturnType> {
|
|
48
|
+
mediaDrop: {
|
|
49
|
+
/**
|
|
50
|
+
* Route dropped/provided files by type and count (the single drop
|
|
51
|
+
* pipeline). Inserts at the current selection — callers that want a
|
|
52
|
+
* precise position should move the selection first.
|
|
53
|
+
*/
|
|
54
|
+
dropFiles: (files: File[]) => ReturnType
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const MediaDrop = Extension.create({
|
|
60
|
+
name: 'mediaDrop',
|
|
61
|
+
|
|
62
|
+
addCommands() {
|
|
63
|
+
return {
|
|
64
|
+
dropFiles:
|
|
65
|
+
(files: File[]) =>
|
|
66
|
+
({ editor }) => {
|
|
67
|
+
if (files.length === 0) return false
|
|
68
|
+
|
|
69
|
+
const images = files.filter((f) => IMAGE_RE.test(f.type))
|
|
70
|
+
const videos = files.filter((f) => VIDEO_RE.test(f.type))
|
|
71
|
+
// Everything that is neither image nor video becomes an attachment.
|
|
72
|
+
const others = files.filter(
|
|
73
|
+
(f) => !IMAGE_RE.test(f.type) && !VIDEO_RE.test(f.type),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const can = (name: string): boolean =>
|
|
77
|
+
typeof (editor.commands as Record<string, unknown>)[name] ===
|
|
78
|
+
'function'
|
|
79
|
+
|
|
80
|
+
let handled = false
|
|
81
|
+
|
|
82
|
+
if (images.length === 1 && can('uploadImage')) {
|
|
83
|
+
editor.commands.uploadImage(images[0])
|
|
84
|
+
handled = true
|
|
85
|
+
} else if (images.length > 1 && can('uploadImage')) {
|
|
86
|
+
openImageGroupUploadDialog({ editor, files: images })
|
|
87
|
+
handled = true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (videos.length > 0 && can('uploadVideoFiles')) {
|
|
91
|
+
editor.commands.uploadVideoFiles(videos)
|
|
92
|
+
handled = true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (others.length > 0 && can('uploadAttachmentFiles')) {
|
|
96
|
+
editor.commands.uploadAttachmentFiles(others)
|
|
97
|
+
handled = true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return handled
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
addProseMirrorPlugins() {
|
|
106
|
+
const editor = this.editor
|
|
107
|
+
|
|
108
|
+
/** Whether a kit exposes a given routing command. */
|
|
109
|
+
const can = (name: string): boolean =>
|
|
110
|
+
typeof (editor.commands as Record<string, unknown>)[name] === 'function'
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
new Plugin({
|
|
114
|
+
props: {
|
|
115
|
+
handleDOMEvents: {
|
|
116
|
+
drop: (view: EditorView, event: DragEvent): boolean => {
|
|
117
|
+
const files = collectFiles(event.dataTransfer)
|
|
118
|
+
if (files.length === 0) return false
|
|
119
|
+
// Only claim drops we can actually route; let ProseMirror handle
|
|
120
|
+
// the rest. Non-media files route to attachments when available.
|
|
121
|
+
const hasMedia = files.some(
|
|
122
|
+
(f) => IMAGE_RE.test(f.type) || VIDEO_RE.test(f.type),
|
|
123
|
+
)
|
|
124
|
+
const hasOther = files.some(
|
|
125
|
+
(f) => !IMAGE_RE.test(f.type) && !VIDEO_RE.test(f.type),
|
|
126
|
+
)
|
|
127
|
+
const routable =
|
|
128
|
+
hasMedia || (hasOther && can('uploadAttachmentFiles'))
|
|
129
|
+
if (!routable) return false
|
|
130
|
+
|
|
131
|
+
event.preventDefault()
|
|
132
|
+
event.stopPropagation()
|
|
133
|
+
|
|
134
|
+
// Drop at the cursor: move the selection to the drop point so the
|
|
135
|
+
// single-image insert lands where the file was released.
|
|
136
|
+
const coords = view.posAtCoords({
|
|
137
|
+
left: event.clientX,
|
|
138
|
+
top: event.clientY,
|
|
139
|
+
})
|
|
140
|
+
if (coords) {
|
|
141
|
+
view.dispatch(
|
|
142
|
+
view.state.tr.setSelection(
|
|
143
|
+
Selection.near(view.state.doc.resolve(coords.pos)),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
editor.commands.dropFiles(files)
|
|
149
|
+
return true
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Paste of non-image, non-video files (image/video pastes keep
|
|
154
|
+
// flowing to their own extensions' paste handlers, so we never claim
|
|
155
|
+
// them here). Pure-attachment pastes are uploaded at the cursor.
|
|
156
|
+
handlePaste: (_view, event): boolean => {
|
|
157
|
+
if (!can('uploadAttachmentFiles')) return false
|
|
158
|
+
const files = collectFiles(event.clipboardData)
|
|
159
|
+
if (files.length === 0) return false
|
|
160
|
+
const others = files.filter(
|
|
161
|
+
(f) => !IMAGE_RE.test(f.type) && !VIDEO_RE.test(f.type),
|
|
162
|
+
)
|
|
163
|
+
// Only claim when the paste is exclusively attachments; otherwise
|
|
164
|
+
// defer to the image/video paste handlers.
|
|
165
|
+
if (others.length !== files.length) return false
|
|
166
|
+
|
|
167
|
+
event.preventDefault()
|
|
168
|
+
editor.commands.uploadAttachmentFiles(others)
|
|
169
|
+
return true
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
export default MediaDrop
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
export interface MediaDimensions {
|
|
12
12
|
width: number
|
|
13
13
|
height: number
|
|
14
|
+
poster?: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/** Probe an image source via `<img>.naturalWidth`/`naturalHeight`. */
|
|
@@ -35,21 +36,53 @@ export function probeImageDimensions(src: string): Promise<MediaDimensions> {
|
|
|
35
36
|
})
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
function captureVideoPoster(video: HTMLVideoElement): string | undefined {
|
|
40
|
+
if (!video.videoWidth || !video.videoHeight) return undefined
|
|
41
|
+
const canvas = document.createElement('canvas')
|
|
42
|
+
canvas.width = video.videoWidth
|
|
43
|
+
canvas.height = video.videoHeight
|
|
44
|
+
const ctx = canvas.getContext('2d')
|
|
45
|
+
if (!ctx) return undefined
|
|
46
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
|
47
|
+
return canvas.toDataURL('image/jpeg', 0.72)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Probe a video source via `<video>.videoWidth`/`videoHeight` and a poster frame. */
|
|
39
51
|
export function probeVideoDimensions(src: string): Promise<MediaDimensions> {
|
|
40
52
|
return new Promise<MediaDimensions>((resolve, reject) => {
|
|
41
53
|
const video = document.createElement('video')
|
|
42
54
|
video.preload = 'metadata'
|
|
55
|
+
video.muted = true
|
|
56
|
+
video.playsInline = true
|
|
43
57
|
const cleanup = () => {
|
|
44
58
|
video.onloadedmetadata = null
|
|
59
|
+
video.onseeked = null
|
|
45
60
|
video.onerror = null
|
|
46
61
|
video.removeAttribute('src')
|
|
47
62
|
video.load()
|
|
48
63
|
}
|
|
49
|
-
|
|
64
|
+
const resolveWithPoster = () => {
|
|
50
65
|
const dims = { width: video.videoWidth, height: video.videoHeight }
|
|
66
|
+
let poster: string | undefined
|
|
67
|
+
try {
|
|
68
|
+
poster = captureVideoPoster(video)
|
|
69
|
+
} catch {
|
|
70
|
+
poster = undefined
|
|
71
|
+
}
|
|
51
72
|
cleanup()
|
|
52
|
-
resolve(dims)
|
|
73
|
+
resolve({ ...dims, poster })
|
|
74
|
+
}
|
|
75
|
+
video.onloadedmetadata = () => {
|
|
76
|
+
if (Number.isFinite(video.duration) && video.duration > 0.15) {
|
|
77
|
+
video.onseeked = resolveWithPoster
|
|
78
|
+
try {
|
|
79
|
+
video.currentTime = 0.1
|
|
80
|
+
} catch {
|
|
81
|
+
resolveWithPoster()
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
resolveWithPoster()
|
|
85
|
+
}
|
|
53
86
|
}
|
|
54
87
|
video.onerror = () => {
|
|
55
88
|
cleanup()
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
export interface OptionalDimensions {
|
|
23
23
|
width: number | null
|
|
24
24
|
height: number | null
|
|
25
|
+
poster?: string
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -76,8 +77,10 @@ export function insertPlaceholder(
|
|
|
76
77
|
mode: InsertMode,
|
|
77
78
|
uploadId: string,
|
|
78
79
|
dims: OptionalDimensions,
|
|
80
|
+
attrs: Record<string, unknown> = {},
|
|
79
81
|
): void {
|
|
80
82
|
const node = view.state.schema.nodes[nodeName].create({
|
|
83
|
+
...attrs,
|
|
81
84
|
loading: true,
|
|
82
85
|
uploadId,
|
|
83
86
|
src: null,
|
|
@@ -99,6 +102,19 @@ export function insertPlaceholder(
|
|
|
99
102
|
dispatchIfAlive(view, tr)
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
/** Remove a loading placeholder after user cancellation. */
|
|
106
|
+
export function removeNodeByUploadId(
|
|
107
|
+
view: EditorView,
|
|
108
|
+
nodeName: string,
|
|
109
|
+
uploadId: string,
|
|
110
|
+
): void {
|
|
111
|
+
const pos = findNodeByUploadId(view, nodeName, uploadId)
|
|
112
|
+
if (pos === null) return
|
|
113
|
+
const node = view.state.doc.nodeAt(pos)
|
|
114
|
+
if (!node) return
|
|
115
|
+
dispatchIfAlive(view, view.state.tr.delete(pos, pos + node.nodeSize))
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
/** Write the uploaded src/dimensions back onto the placeholder node. */
|
|
103
119
|
export function applyUploadSuccess(
|
|
104
120
|
view: EditorView,
|
|
@@ -118,6 +134,7 @@ export function applyUploadSuccess(
|
|
|
118
134
|
width: uploaded.width || node.attrs.width,
|
|
119
135
|
height: uploaded.height || node.attrs.height,
|
|
120
136
|
loading: false,
|
|
137
|
+
error: null,
|
|
121
138
|
}),
|
|
122
139
|
)
|
|
123
140
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
6
|
+
|
|
7
|
+
vi.mock('#molecules/editor/extensions/shared/node-view', () => ({
|
|
8
|
+
dispatchIfAlive: vi.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
import { createMediaPlugin } from '#molecules/editor/extensions/shared/media-plugin'
|
|
12
|
+
import { dispatchIfAlive } from '#molecules/editor/extensions/shared/node-view'
|
|
13
|
+
import type { MediaUploadConfig } from '#molecules/editor/extensions/shared/media-upload-types'
|
|
14
|
+
|
|
15
|
+
function mediaNode(src: string, attrs: Record<string, unknown> = {}) {
|
|
16
|
+
return {
|
|
17
|
+
type: { name: 'image' },
|
|
18
|
+
attrs: { src, ...attrs },
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('media plugin dimension backfill', () => {
|
|
23
|
+
it('does not write stale dimensions when the node at the captured position changed source', async () => {
|
|
24
|
+
const inserted = mediaNode('/files/old.png')
|
|
25
|
+
const live = mediaNode('/files/new.png')
|
|
26
|
+
const config: MediaUploadConfig = {
|
|
27
|
+
nodeName: 'image',
|
|
28
|
+
accept: /image/i,
|
|
29
|
+
storeBase64: true,
|
|
30
|
+
probeDimensions: vi.fn(async () => ({ width: 100, height: 50 })),
|
|
31
|
+
}
|
|
32
|
+
const view = {
|
|
33
|
+
state: {
|
|
34
|
+
doc: { nodeAt: () => live },
|
|
35
|
+
tr: { setNodeMarkup: vi.fn() },
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
const plugin = createMediaPlugin({} as never, config, {
|
|
39
|
+
editor: { view } as never,
|
|
40
|
+
options: {},
|
|
41
|
+
})
|
|
42
|
+
const newState = {
|
|
43
|
+
doc: {
|
|
44
|
+
descendants: (visit: (node: unknown, pos: number) => void) => {
|
|
45
|
+
visit(inserted, 5)
|
|
46
|
+
},
|
|
47
|
+
nodeAt: () => inserted,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
;(plugin as any).spec.appendTransaction(
|
|
52
|
+
[{ docChanged: true }],
|
|
53
|
+
{} as never,
|
|
54
|
+
newState,
|
|
55
|
+
)
|
|
56
|
+
await Promise.resolve()
|
|
57
|
+
|
|
58
|
+
expect(dispatchIfAlive).not.toHaveBeenCalled()
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* is handled under `props.handlePaste` (NOT `handleDOMEvents.paste`), per the
|
|
9
9
|
* conventions §1.6 contract.
|
|
10
10
|
*/
|
|
11
|
-
import { Plugin
|
|
11
|
+
import { Plugin } from '@tiptap/pm/state'
|
|
12
12
|
import type { Transaction, EditorState } from '@tiptap/pm/state'
|
|
13
13
|
import type { EditorView } from '@tiptap/pm/view'
|
|
14
14
|
import type { Editor } from '@tiptap/core'
|
|
@@ -61,42 +61,11 @@ export function createMediaPlugin(
|
|
|
61
61
|
|
|
62
62
|
return new Plugin({
|
|
63
63
|
props: {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
!editor ||
|
|
70
|
-
!event.dataTransfer?.files?.length ||
|
|
71
|
-
!options.uploadFunction
|
|
72
|
-
) {
|
|
73
|
-
return false
|
|
74
|
-
}
|
|
75
|
-
const files = collectFiles(event.dataTransfer, config.accept)
|
|
76
|
-
if (files.length === 0) return false
|
|
77
|
-
|
|
78
|
-
event.preventDefault()
|
|
79
|
-
|
|
80
|
-
let pos: number | null = null
|
|
81
|
-
const coords = view.posAtCoords({
|
|
82
|
-
left: event.clientX,
|
|
83
|
-
top: event.clientY,
|
|
84
|
-
})
|
|
85
|
-
if (coords) {
|
|
86
|
-
pos = coords.pos
|
|
87
|
-
view.dispatch(
|
|
88
|
-
view.state.tr.setSelection(
|
|
89
|
-
Selection.near(view.state.doc.resolve(pos)),
|
|
90
|
-
),
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
void engine.processMultiple(files, editor, pos, options)
|
|
95
|
-
return true
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
handlePaste: (view: EditorView, event: ClipboardEvent): boolean => {
|
|
64
|
+
// NOTE: drop is NOT handled here. The single drop pipeline lives in the
|
|
65
|
+
// `MediaDrop` extension (`media-drop-extension.ts`), which routes a whole
|
|
66
|
+
// drop by type/count. This per-node plugin owns only paste + the
|
|
67
|
+
// intrinsic-dimension back-fill below.
|
|
68
|
+
handlePaste: (_view: EditorView, event: ClipboardEvent): boolean => {
|
|
100
69
|
const editor = host.editor
|
|
101
70
|
const options = resolve()
|
|
102
71
|
if (!editor || !options.uploadFunction) return false
|
|
@@ -145,6 +114,7 @@ export function createMediaPlugin(
|
|
|
145
114
|
const view = editor.view
|
|
146
115
|
const live = view.state.doc.nodeAt(pos)
|
|
147
116
|
if (!live || live.type.name !== config.nodeName) return
|
|
117
|
+
if (live.attrs.src !== src) return
|
|
148
118
|
if (live.attrs.width != null && live.attrs.height != null) return
|
|
149
119
|
dispatchIfAlive(
|
|
150
120
|
view,
|