@openreachtech/furo-vue 1.3.1
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/LICENSE +9 -0
- package/README.ja.md +334 -0
- package/README.md +333 -0
- package/index.js +11 -0
- package/lib/assets/css/furo/0010.variables-palette-color-scale.css +298 -0
- package/lib/assets/css/furo/0020.variables-z-index.css +10 -0
- package/lib/assets/css/furo/0030.variables-semantic-color.css +178 -0
- package/lib/assets/css/furo/0035.variables-semantic-dimension.css +104 -0
- package/lib/assets/css/furo/0040.variables-component-furo.css +28 -0
- package/lib/assets/css/furo/0050.furo-editor-content.css +22 -0
- package/lib/assets/css/furo.css +15 -0
- package/lib/components/atoms/FuroAvatar/FuroAvatar.vue +135 -0
- package/lib/components/atoms/FuroAvatar/FuroAvatarContext.js +147 -0
- package/lib/components/atoms/FuroBadge/FuroBadge.vue +240 -0
- package/lib/components/atoms/FuroBadge/FuroBadgeContext.js +298 -0
- package/lib/components/atoms/FuroButton/ButtonEmitPayload.js +44 -0
- package/lib/components/atoms/FuroButton/FuroButton.vue +301 -0
- package/lib/components/atoms/FuroButton/FuroButtonContext.js +328 -0
- package/lib/components/atoms/FuroCheckbox/CheckboxEmitPayload.js +106 -0
- package/lib/components/atoms/FuroCheckbox/FuroCheckbox.vue +173 -0
- package/lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js +682 -0
- package/lib/components/atoms/FuroEmailField/EmailFieldEmitPayload.js +74 -0
- package/lib/components/atoms/FuroEmailField/FuroEmailField.vue +119 -0
- package/lib/components/atoms/FuroEmailField/FuroEmailFieldContext.js +311 -0
- package/lib/components/atoms/FuroFileField/FileFieldEmitPayload.js +80 -0
- package/lib/components/atoms/FuroFileField/FuroFileField.vue +128 -0
- package/lib/components/atoms/FuroFileField/FuroFileFieldContext.js +195 -0
- package/lib/components/atoms/FuroIcon/FuroIcon.vue +74 -0
- package/lib/components/atoms/FuroIcon/FuroIconContext.js +293 -0
- package/lib/components/atoms/FuroIcon/FuroIconRegistry.js +617 -0
- package/lib/components/atoms/FuroIcon/FuroIconifyRendererContext.js +101 -0
- package/lib/components/atoms/FuroIcon/furoIconData.js +164 -0
- package/lib/components/atoms/FuroIcon/iconRenderers.js +62 -0
- package/lib/components/atoms/FuroIcon/icons.js +7 -0
- package/lib/components/atoms/FuroNumberField/FuroNumberField.vue +213 -0
- package/lib/components/atoms/FuroNumberField/FuroNumberFieldContext.js +442 -0
- package/lib/components/atoms/FuroNumberField/NumberFieldEmitPayload.js +162 -0
- package/lib/components/atoms/FuroPasswordField/FuroPasswordField.vue +119 -0
- package/lib/components/atoms/FuroPasswordField/FuroPasswordFieldContext.js +311 -0
- package/lib/components/atoms/FuroPasswordField/PasswordFieldEmitPayload.js +74 -0
- package/lib/components/atoms/FuroRadio/FuroRadio.vue +193 -0
- package/lib/components/atoms/FuroRadio/FuroRadioContext.js +557 -0
- package/lib/components/atoms/FuroRadio/RadioEmitPayload.js +72 -0
- package/lib/components/atoms/FuroSwitch/FuroSwitch.vue +165 -0
- package/lib/components/atoms/FuroSwitch/FuroSwitchContext.js +440 -0
- package/lib/components/atoms/FuroSwitch/SwitchEmitPayload.js +86 -0
- package/lib/components/atoms/FuroTextField/FuroTextField.vue +119 -0
- package/lib/components/atoms/FuroTextField/FuroTextFieldContext.js +311 -0
- package/lib/components/atoms/FuroTextField/TextFieldEmitPayload.js +83 -0
- package/lib/components/atoms/FuroTextarea/FuroTextarea.vue +121 -0
- package/lib/components/atoms/FuroTextarea/FuroTextareaContext.js +322 -0
- package/lib/components/atoms/FuroTextarea/TextareaEmitPayload.js +83 -0
- package/lib/components/atoms/FuroToggle/FuroToggle.vue +172 -0
- package/lib/components/atoms/FuroToggle/FuroToggleContext.js +410 -0
- package/lib/components/atoms/FuroToggle/ToggleEmitPayload.js +86 -0
- package/lib/components/molecules/FuroAccordion/AccordionEmitPayload.js +80 -0
- package/lib/components/molecules/FuroAccordion/FuroAccordion.vue +219 -0
- package/lib/components/molecules/FuroAccordion/FuroAccordionContext.js +547 -0
- package/lib/components/molecules/FuroAttachment/FuroAttachment.vue +376 -0
- package/lib/components/molecules/FuroAttachment/FuroAttachmentContext.js +628 -0
- package/lib/components/molecules/FuroAutocompleteField/AutocompleteEmitPayload.js +225 -0
- package/lib/components/molecules/FuroAutocompleteField/FuroAutocompleteField.vue +669 -0
- package/lib/components/molecules/FuroAutocompleteField/FuroAutocompleteFieldContext.js +1525 -0
- package/lib/components/molecules/FuroAvatarGroup/FuroAvatarGroup.vue +126 -0
- package/lib/components/molecules/FuroAvatarGroup/FuroAvatarGroupContext.js +245 -0
- package/lib/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue +219 -0
- package/lib/components/molecules/FuroBreadcrumb/FuroBreadcrumbContext.js +257 -0
- package/lib/components/molecules/FuroCollapsible/CollapsibleEmitPayload.js +69 -0
- package/lib/components/molecules/FuroCollapsible/FuroCollapsible.vue +177 -0
- package/lib/components/molecules/FuroCollapsible/FuroCollapsibleContext.js +389 -0
- package/lib/components/molecules/FuroControlBlock/FuroControlBlock.vue +149 -0
- package/lib/components/molecules/FuroControlBlock/FuroControlBlockContext.js +81 -0
- package/lib/components/molecules/FuroDatePicker/DatePickerEmitPayload.js +74 -0
- package/lib/components/molecules/FuroDatePicker/FuroDatePicker.vue +661 -0
- package/lib/components/molecules/FuroDatePicker/FuroDatePickerContext.js +1038 -0
- package/lib/components/molecules/FuroDateTimePicker/DateTimePickerEmitPayload.js +96 -0
- package/lib/components/molecules/FuroDateTimePicker/FuroDateTimePicker.vue +153 -0
- package/lib/components/molecules/FuroDateTimePicker/FuroDateTimePickerContext.js +840 -0
- package/lib/components/molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js +100 -0
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue +348 -0
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.js +714 -0
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuOptionRows.vue +222 -0
- package/lib/components/molecules/FuroEditableField/EditableEmitPayload.js +89 -0
- package/lib/components/molecules/FuroEditableField/FuroEditableField.vue +314 -0
- package/lib/components/molecules/FuroEditableField/FuroEditableFieldContext.js +1018 -0
- package/lib/components/molecules/FuroEditableField/processors/EditorCollisionProcessor.js +236 -0
- package/lib/components/molecules/FuroEditableField/processors/ObjectProcessor.js +101 -0
- package/lib/components/molecules/FuroEmptyState/FuroEmptyState.vue +124 -0
- package/lib/components/molecules/FuroEmptyState/FuroEmptyStateContext.js +42 -0
- package/lib/components/molecules/FuroErrorState/FuroErrorState.vue +125 -0
- package/lib/components/molecules/FuroErrorState/FuroErrorStateContext.js +42 -0
- package/lib/components/molecules/FuroPagination/FuroPagination.vue +246 -0
- package/lib/components/molecules/FuroPagination/FuroPaginationContext.js +489 -0
- package/lib/components/molecules/FuroPopover/FuroPopover.vue +171 -0
- package/lib/components/molecules/FuroPopover/FuroPopoverContext.js +279 -0
- package/lib/components/molecules/FuroScrollArea/FuroScrollArea.vue +181 -0
- package/lib/components/molecules/FuroScrollArea/FuroScrollAreaContext.js +149 -0
- package/lib/components/molecules/FuroSearchField/FuroSearchField.vue +155 -0
- package/lib/components/molecules/FuroSearchField/FuroSearchFieldContext.js +231 -0
- package/lib/components/molecules/FuroSectionHeader/FuroSectionHeader.vue +140 -0
- package/lib/components/molecules/FuroSectionHeader/FuroSectionHeaderContext.js +204 -0
- package/lib/components/molecules/FuroSelect/FuroSelect.vue +474 -0
- package/lib/components/molecules/FuroSelect/FuroSelectContext.js +743 -0
- package/lib/components/molecules/FuroSelect/SelectEmitPayload.js +181 -0
- package/lib/components/molecules/FuroSeparator/FuroSeparator.vue +88 -0
- package/lib/components/molecules/FuroSeparator/FuroSeparatorContext.js +40 -0
- package/lib/components/molecules/FuroSkeleton/FuroSkeleton.vue +101 -0
- package/lib/components/molecules/FuroSkeleton/FuroSkeletonContext.js +82 -0
- package/lib/components/molecules/FuroSplitter/FuroSplitter.vue +237 -0
- package/lib/components/molecules/FuroSplitter/FuroSplitterContext.js +429 -0
- package/lib/components/molecules/FuroSplitter/SplitterEmitPayload.js +69 -0
- package/lib/components/molecules/FuroTimeField/FuroTimeField.vue +251 -0
- package/lib/components/molecules/FuroTimeField/FuroTimeFieldContext.js +1138 -0
- package/lib/components/molecules/FuroTimeField/TimeFieldEmitPayload.js +74 -0
- package/lib/components/molecules/FuroToggleGroup/FuroToggleGroup.vue +211 -0
- package/lib/components/molecules/FuroToggleGroup/FuroToggleGroupContext.js +551 -0
- package/lib/components/molecules/FuroToggleGroup/ToggleGroupEmitPayload.js +80 -0
- package/lib/components/molecules/FuroToolBar/FuroToolBar.vue +91 -0
- package/lib/components/molecules/FuroToolBar/FuroToolBarContext.js +75 -0
- package/lib/components/molecules/FuroTooltip/FuroTooltip.vue +183 -0
- package/lib/components/molecules/FuroTooltip/FuroTooltipContext.js +290 -0
- package/lib/components/organisms/FuroAlertDialog/FuroAlertDialog.vue +324 -0
- package/lib/components/organisms/FuroAlertDialog/FuroAlertDialogContext.js +608 -0
- package/lib/components/organisms/FuroDialog/FuroDialog.vue +362 -0
- package/lib/components/organisms/FuroDialog/FuroDialogContext.js +535 -0
- package/lib/components/organisms/FuroDrawer/FuroDrawer.vue +618 -0
- package/lib/components/organisms/FuroDrawer/FuroDrawerContext.js +676 -0
- package/lib/components/organisms/FuroEditor/FuroEditor.vue +613 -0
- package/lib/components/organisms/FuroEditor/FuroEditorCommandContext.js +420 -0
- package/lib/components/organisms/FuroEditor/FuroEditorContext.js +2048 -0
- package/lib/components/organisms/FuroEditor/FuroEditorMentionMenu.vue +120 -0
- package/lib/components/organisms/FuroEditor/FuroEditorMentionMenuContext.js +261 -0
- package/lib/components/organisms/FuroEditor/PreserveNewlinesPasteExtension.js +267 -0
- package/lib/components/organisms/FuroEditor/build-editor-extensions.js +97 -0
- package/lib/components/organisms/FuroEditor/mention-suggestion.js +320 -0
- package/lib/components/organisms/FuroEditor/paste-link-segments.js +124 -0
- package/lib/components/organisms/FuroSidebar/FuroSidebar.vue +473 -0
- package/lib/components/organisms/FuroSidebar/FuroSidebarContext.js +1014 -0
- package/lib/components/organisms/FuroStepper/FuroStepper.vue +345 -0
- package/lib/components/organisms/FuroStepper/FuroStepperContext.js +481 -0
- package/lib/components/organisms/FuroTable/FuroTable.vue +707 -0
- package/lib/components/organisms/FuroTable/FuroTableContext.js +1965 -0
- package/lib/components/organisms/FuroTable/FuroTableRowItemContext.js +111 -0
- package/lib/components/organisms/FuroTabs/FuroTabs.vue +403 -0
- package/lib/components/organisms/FuroTabs/FuroTabsContext.js +846 -0
- package/lib/components/organisms/FuroToast/FuroToast.vue +624 -0
- package/lib/components/organisms/FuroToast/FuroToastContext.js +1089 -0
- package/lib/components/organisms/FuroToast/FuroToaster.vue +44 -0
- package/lib/components/organisms/FuroToast/FuroToasterContext.js +113 -0
- package/lib/components/organisms/FuroToast/ToastQueue.js +325 -0
- package/lib/components/organisms/FuroToast/toast.js +64 -0
- package/lib/contexts/BaseFuroContext.js +318 -0
- package/lib/contexts/BaseFuroContextAccessor.js +47 -0
- package/lib/emit-payload/BaseEmitPayload.js +91 -0
- package/lib/index.components.js +61 -0
- package/lib/index.core.js +18 -0
- package/lib/tools/TimerClerk.js +75 -0
- package/nuxt/index.js +109 -0
- package/nuxt/runtime/furo-icon-renderer.js +24 -0
- package/package.json +137 -0
- package/public/furo-vue/components.json +4619 -0
- package/public/furo-vue/llms.txt +66 -0
- package/types/furo-nuxt.d.ts +48 -0
- package/types/generated/components/atoms/FuroAvatar/FuroAvatar.vue.d.ts +74 -0
- package/types/generated/components/atoms/FuroAvatar/FuroAvatarContext.d.ts +82 -0
- package/types/generated/components/atoms/FuroBadge/FuroBadge.vue.d.ts +12 -0
- package/types/generated/components/atoms/FuroBadge/FuroBadgeContext.d.ts +160 -0
- package/types/generated/components/atoms/FuroButton/ButtonEmitPayload.d.ts +19 -0
- package/types/generated/components/atoms/FuroButton/FuroButton.vue.d.ts +53 -0
- package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +156 -0
- package/types/generated/components/atoms/FuroCheckbox/CheckboxEmitPayload.d.ts +55 -0
- package/types/generated/components/atoms/FuroCheckbox/FuroCheckbox.vue.d.ts +87 -0
- package/types/generated/components/atoms/FuroCheckbox/FuroCheckboxContext.d.ts +342 -0
- package/types/generated/components/atoms/FuroEmailField/EmailFieldEmitPayload.d.ts +46 -0
- package/types/generated/components/atoms/FuroEmailField/FuroEmailField.vue.d.ts +458 -0
- package/types/generated/components/atoms/FuroEmailField/FuroEmailFieldContext.d.ts +161 -0
- package/types/generated/components/atoms/FuroFileField/FileFieldEmitPayload.d.ts +46 -0
- package/types/generated/components/atoms/FuroFileField/FuroFileField.vue.d.ts +454 -0
- package/types/generated/components/atoms/FuroFileField/FuroFileFieldContext.d.ts +109 -0
- package/types/generated/components/atoms/FuroIcon/FuroIcon.vue.d.ts +14 -0
- package/types/generated/components/atoms/FuroIcon/FuroIconContext.d.ts +152 -0
- package/types/generated/components/atoms/FuroIcon/FuroIconRegistry.d.ts +457 -0
- package/types/generated/components/atoms/FuroIcon/FuroIconifyRendererContext.d.ts +65 -0
- package/types/generated/components/atoms/FuroIcon/furoIconData.d.ts +154 -0
- package/types/generated/components/atoms/FuroIcon/iconRenderers.d.ts +6 -0
- package/types/generated/components/atoms/FuroIcon/icons.d.ts +6 -0
- package/types/generated/components/atoms/FuroNumberField/FuroNumberField.vue.d.ts +500 -0
- package/types/generated/components/atoms/FuroNumberField/FuroNumberFieldContext.d.ts +243 -0
- package/types/generated/components/atoms/FuroNumberField/NumberFieldEmitPayload.d.ts +74 -0
- package/types/generated/components/atoms/FuroPasswordField/FuroPasswordField.vue.d.ts +458 -0
- package/types/generated/components/atoms/FuroPasswordField/FuroPasswordFieldContext.d.ts +161 -0
- package/types/generated/components/atoms/FuroPasswordField/PasswordFieldEmitPayload.d.ts +46 -0
- package/types/generated/components/atoms/FuroRadio/FuroRadio.vue.d.ts +96 -0
- package/types/generated/components/atoms/FuroRadio/FuroRadioContext.d.ts +290 -0
- package/types/generated/components/atoms/FuroRadio/RadioEmitPayload.d.ts +39 -0
- package/types/generated/components/atoms/FuroSwitch/FuroSwitch.vue.d.ts +74 -0
- package/types/generated/components/atoms/FuroSwitch/FuroSwitchContext.d.ts +218 -0
- package/types/generated/components/atoms/FuroSwitch/SwitchEmitPayload.d.ts +42 -0
- package/types/generated/components/atoms/FuroTextField/FuroTextField.vue.d.ts +458 -0
- package/types/generated/components/atoms/FuroTextField/FuroTextFieldContext.d.ts +161 -0
- package/types/generated/components/atoms/FuroTextField/TextFieldEmitPayload.d.ts +52 -0
- package/types/generated/components/atoms/FuroTextarea/FuroTextarea.vue.d.ts +422 -0
- package/types/generated/components/atoms/FuroTextarea/FuroTextareaContext.d.ts +161 -0
- package/types/generated/components/atoms/FuroTextarea/TextareaEmitPayload.d.ts +52 -0
- package/types/generated/components/atoms/FuroToggle/FuroToggle.vue.d.ts +51 -0
- package/types/generated/components/atoms/FuroToggle/FuroToggleContext.d.ts +191 -0
- package/types/generated/components/atoms/FuroToggle/ToggleEmitPayload.d.ts +42 -0
- package/types/generated/components/molecules/FuroAccordion/AccordionEmitPayload.d.ts +41 -0
- package/types/generated/components/molecules/FuroAccordion/FuroAccordion.vue.d.ts +133 -0
- package/types/generated/components/molecules/FuroAccordion/FuroAccordionContext.d.ts +279 -0
- package/types/generated/components/molecules/FuroAttachment/FuroAttachment.vue.d.ts +79 -0
- package/types/generated/components/molecules/FuroAttachment/FuroAttachmentContext.d.ts +363 -0
- package/types/generated/components/molecules/FuroAutocompleteField/AutocompleteEmitPayload.d.ts +129 -0
- package/types/generated/components/molecules/FuroAutocompleteField/FuroAutocompleteField.vue.d.ts +751 -0
- package/types/generated/components/molecules/FuroAutocompleteField/FuroAutocompleteFieldContext.d.ts +722 -0
- package/types/generated/components/molecules/FuroAvatarGroup/FuroAvatarGroup.vue.d.ts +84 -0
- package/types/generated/components/molecules/FuroAvatarGroup/FuroAvatarGroupContext.d.ts +150 -0
- package/types/generated/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue.d.ts +24 -0
- package/types/generated/components/molecules/FuroBreadcrumb/FuroBreadcrumbContext.d.ts +152 -0
- package/types/generated/components/molecules/FuroCollapsible/CollapsibleEmitPayload.d.ts +36 -0
- package/types/generated/components/molecules/FuroCollapsible/FuroCollapsible.vue.d.ts +98 -0
- package/types/generated/components/molecules/FuroCollapsible/FuroCollapsibleContext.d.ts +191 -0
- package/types/generated/components/molecules/FuroControlBlock/FuroControlBlock.vue.d.ts +30 -0
- package/types/generated/components/molecules/FuroControlBlock/FuroControlBlockContext.d.ts +57 -0
- package/types/generated/components/molecules/FuroDatePicker/DatePickerEmitPayload.d.ts +41 -0
- package/types/generated/components/molecules/FuroDatePicker/FuroDatePicker.vue.d.ts +456 -0
- package/types/generated/components/molecules/FuroDatePicker/FuroDatePickerContext.d.ts +516 -0
- package/types/generated/components/molecules/FuroDateTimePicker/DateTimePickerEmitPayload.d.ts +55 -0
- package/types/generated/components/molecules/FuroDateTimePicker/FuroDateTimePicker.vue.d.ts +1365 -0
- package/types/generated/components/molecules/FuroDateTimePicker/FuroDateTimePickerContext.d.ts +435 -0
- package/types/generated/components/molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.d.ts +48 -0
- package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue.d.ts +773 -0
- package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.d.ts +380 -0
- package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenuOptionRows.vue.d.ts +261 -0
- package/types/generated/components/molecules/FuroEditableField/EditableEmitPayload.d.ts +39 -0
- package/types/generated/components/molecules/FuroEditableField/FuroEditableField.vue.d.ts +646 -0
- package/types/generated/components/molecules/FuroEditableField/FuroEditableFieldContext.d.ts +477 -0
- package/types/generated/components/molecules/FuroEditableField/processors/EditorCollisionProcessor.d.ts +121 -0
- package/types/generated/components/molecules/FuroEditableField/processors/ObjectProcessor.d.ts +45 -0
- package/types/generated/components/molecules/FuroEmptyState/FuroEmptyState.vue.d.ts +21 -0
- package/types/generated/components/molecules/FuroEmptyState/FuroEmptyStateContext.d.ts +32 -0
- package/types/generated/components/molecules/FuroErrorState/FuroErrorState.vue.d.ts +21 -0
- package/types/generated/components/molecules/FuroErrorState/FuroErrorStateContext.d.ts +32 -0
- package/types/generated/components/molecules/FuroPagination/FuroPagination.vue.d.ts +534 -0
- package/types/generated/components/molecules/FuroPagination/FuroPaginationContext.d.ts +271 -0
- package/types/generated/components/molecules/FuroPopover/FuroPopover.vue.d.ts +513 -0
- package/types/generated/components/molecules/FuroPopover/FuroPopoverContext.d.ts +160 -0
- package/types/generated/components/molecules/FuroScrollArea/FuroScrollArea.vue.d.ts +124 -0
- package/types/generated/components/molecules/FuroScrollArea/FuroScrollAreaContext.d.ts +86 -0
- package/types/generated/components/molecules/FuroSearchField/FuroSearchField.vue.d.ts +487 -0
- package/types/generated/components/molecules/FuroSearchField/FuroSearchFieldContext.d.ts +137 -0
- package/types/generated/components/molecules/FuroSectionHeader/FuroSectionHeader.vue.d.ts +49 -0
- package/types/generated/components/molecules/FuroSectionHeader/FuroSectionHeaderContext.d.ts +121 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +719 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +402 -0
- package/types/generated/components/molecules/FuroSelect/SelectEmitPayload.d.ts +102 -0
- package/types/generated/components/molecules/FuroSeparator/FuroSeparator.vue.d.ts +39 -0
- package/types/generated/components/molecules/FuroSeparator/FuroSeparatorContext.d.ts +31 -0
- package/types/generated/components/molecules/FuroSkeleton/FuroSkeleton.vue.d.ts +27 -0
- package/types/generated/components/molecules/FuroSkeleton/FuroSkeletonContext.d.ts +61 -0
- package/types/generated/components/molecules/FuroSplitter/FuroSplitter.vue.d.ts +139 -0
- package/types/generated/components/molecules/FuroSplitter/FuroSplitterContext.d.ts +226 -0
- package/types/generated/components/molecules/FuroSplitter/SplitterEmitPayload.d.ts +41 -0
- package/types/generated/components/molecules/FuroTimeField/FuroTimeField.vue.d.ts +498 -0
- package/types/generated/components/molecules/FuroTimeField/FuroTimeFieldContext.d.ts +555 -0
- package/types/generated/components/molecules/FuroTimeField/TimeFieldEmitPayload.d.ts +41 -0
- package/types/generated/components/molecules/FuroToggleGroup/FuroToggleGroup.vue.d.ts +70 -0
- package/types/generated/components/molecules/FuroToggleGroup/FuroToggleGroupContext.d.ts +270 -0
- package/types/generated/components/molecules/FuroToggleGroup/ToggleGroupEmitPayload.d.ts +41 -0
- package/types/generated/components/molecules/FuroToolBar/FuroToolBar.vue.d.ts +30 -0
- package/types/generated/components/molecules/FuroToolBar/FuroToolBarContext.d.ts +48 -0
- package/types/generated/components/molecules/FuroTooltip/FuroTooltip.vue.d.ts +524 -0
- package/types/generated/components/molecules/FuroTooltip/FuroTooltipContext.d.ts +166 -0
- package/types/generated/components/organisms/FuroAlertDialog/FuroAlertDialog.vue.d.ts +651 -0
- package/types/generated/components/organisms/FuroAlertDialog/FuroAlertDialogContext.d.ts +299 -0
- package/types/generated/components/organisms/FuroDialog/FuroDialog.vue.d.ts +583 -0
- package/types/generated/components/organisms/FuroDialog/FuroDialogContext.d.ts +264 -0
- package/types/generated/components/organisms/FuroDrawer/FuroDrawer.vue.d.ts +635 -0
- package/types/generated/components/organisms/FuroDrawer/FuroDrawerContext.d.ts +347 -0
- package/types/generated/components/organisms/FuroEditor/FuroEditor.vue.d.ts +2487 -0
- package/types/generated/components/organisms/FuroEditor/FuroEditorCommandContext.d.ts +192 -0
- package/types/generated/components/organisms/FuroEditor/FuroEditorContext.d.ts +904 -0
- package/types/generated/components/organisms/FuroEditor/FuroEditorMentionMenu.vue.d.ts +43 -0
- package/types/generated/components/organisms/FuroEditor/FuroEditorMentionMenuContext.d.ts +159 -0
- package/types/generated/components/organisms/FuroEditor/PreserveNewlinesPasteExtension.d.ts +43 -0
- package/types/generated/components/organisms/FuroEditor/build-editor-extensions.d.ts +22 -0
- package/types/generated/components/organisms/FuroEditor/mention-suggestion.d.ts +25 -0
- package/types/generated/components/organisms/FuroEditor/paste-link-segments.d.ts +31 -0
- package/types/generated/components/organisms/FuroSidebar/FuroSidebar.vue.d.ts +898 -0
- package/types/generated/components/organisms/FuroSidebar/FuroSidebarContext.d.ts +596 -0
- package/types/generated/components/organisms/FuroStepper/FuroStepper.vue.d.ts +589 -0
- package/types/generated/components/organisms/FuroStepper/FuroStepperContext.d.ts +266 -0
- package/types/generated/components/organisms/FuroTable/FuroTable.vue.d.ts +528 -0
- package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +1035 -0
- package/types/generated/components/organisms/FuroTable/FuroTableRowItemContext.d.ts +62 -0
- package/types/generated/components/organisms/FuroTabs/FuroTabs.vue.d.ts +515 -0
- package/types/generated/components/organisms/FuroTabs/FuroTabsContext.d.ts +438 -0
- package/types/generated/components/organisms/FuroToast/FuroToast.vue.d.ts +725 -0
- package/types/generated/components/organisms/FuroToast/FuroToastContext.d.ts +543 -0
- package/types/generated/components/organisms/FuroToast/FuroToaster.vue.d.ts +729 -0
- package/types/generated/components/organisms/FuroToast/FuroToasterContext.d.ts +62 -0
- package/types/generated/components/organisms/FuroToast/ToastQueue.d.ts +201 -0
- package/types/generated/components/organisms/FuroToast/toast.d.ts +23 -0
- package/types/generated/contexts/BaseFuroContext.d.ts +207 -0
- package/types/generated/contexts/BaseFuroContextAccessor.d.ts +28 -0
- package/types/generated/emit-payload/BaseEmitPayload.d.ts +62 -0
- package/types/generated/index.components.d.ts +50 -0
- package/types/generated/index.core.d.ts +6 -0
- package/types/generated/tools/TimerClerk.d.ts +50 -0
- package/types/index.d.ts +12 -0
|
@@ -0,0 +1,2048 @@
|
|
|
1
|
+
import {
|
|
2
|
+
reactive,
|
|
3
|
+
nextTick,
|
|
4
|
+
onMounted,
|
|
5
|
+
} from 'vue'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
marked,
|
|
9
|
+
} from 'marked'
|
|
10
|
+
|
|
11
|
+
import BaseFuroContext from '../../../contexts/BaseFuroContext.js'
|
|
12
|
+
|
|
13
|
+
import FuroEditorCommandContext from './FuroEditorCommandContext.js'
|
|
14
|
+
|
|
15
|
+
const DEFAULT_LABELS = {
|
|
16
|
+
normalText: 'Normal text',
|
|
17
|
+
heading1: 'Heading 1',
|
|
18
|
+
heading2: 'Heading 2',
|
|
19
|
+
heading3: 'Heading 3',
|
|
20
|
+
heading4: 'Heading 4',
|
|
21
|
+
colorDefault: 'Default',
|
|
22
|
+
colorRed: 'Red',
|
|
23
|
+
colorBlue: 'Blue',
|
|
24
|
+
colorGreen: 'Green',
|
|
25
|
+
colorOrange: 'Orange',
|
|
26
|
+
colorPurple: 'Purple',
|
|
27
|
+
alignLeft: 'Align left',
|
|
28
|
+
alignCenter: 'Align center',
|
|
29
|
+
alignRight: 'Align right',
|
|
30
|
+
alignJustify: 'Justify',
|
|
31
|
+
heading: 'Text style',
|
|
32
|
+
textColor: 'Text color',
|
|
33
|
+
textAlign: 'Text alignment',
|
|
34
|
+
bold: 'Bold',
|
|
35
|
+
italic: 'Italic',
|
|
36
|
+
underline: 'Underline',
|
|
37
|
+
strike: 'Strikethrough',
|
|
38
|
+
bullets: 'Bullet list',
|
|
39
|
+
numbers: 'Numbered list',
|
|
40
|
+
blockquote: 'Quote',
|
|
41
|
+
link: 'Link',
|
|
42
|
+
mention: 'Mention',
|
|
43
|
+
image: 'Insert image',
|
|
44
|
+
overflow: 'More options',
|
|
45
|
+
linkDialogTitle: 'Add link',
|
|
46
|
+
linkUrlLabel: 'Link',
|
|
47
|
+
linkDisplayTextLabel: 'Display text (optional)',
|
|
48
|
+
linkSave: 'Save',
|
|
49
|
+
linkCancel: 'Cancel',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const OVERFLOW_TOOLBAR_NAME = 'overflow'
|
|
53
|
+
|
|
54
|
+
const FEATURE_TOOLBAR_NAMES = [
|
|
55
|
+
'image',
|
|
56
|
+
'mention',
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
const COLOR_OPTION_PREFIX = 'color:'
|
|
60
|
+
|
|
61
|
+
const COLOR_VALUES = {
|
|
62
|
+
red: '#ef4444',
|
|
63
|
+
blue: '#3b82f6',
|
|
64
|
+
green: '#10b981',
|
|
65
|
+
orange: '#f97316',
|
|
66
|
+
purple: '#8b5cf6',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const MARKDOWN_PATTERNS = [
|
|
70
|
+
/^#{1,6}\s/u,
|
|
71
|
+
/\*\*.*\*\*/u,
|
|
72
|
+
/\*.*\*/u,
|
|
73
|
+
/\[.*\]\(.*\)/u,
|
|
74
|
+
/^-\s/u,
|
|
75
|
+
/```.*```/su,
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Context for the FuroEditor organism.
|
|
80
|
+
*
|
|
81
|
+
* Owns the editor lifecycle, the value/attachment surface, the formatting toolbar
|
|
82
|
+
* model, and the link dialog. Selection and command execution are delegated to a
|
|
83
|
+
* {@link FuroEditorCommandContext} sub-context. Paste behavior (markdown detection,
|
|
84
|
+
* font-style stripping, newline preservation) is wired onto the editor on mount.
|
|
85
|
+
*
|
|
86
|
+
* Domain concerns (mention search, attachment uploads) are exposed as adapter
|
|
87
|
+
* callbacks and slots — this component never touches GraphQL or domain schemas.
|
|
88
|
+
*
|
|
89
|
+
* @extends {BaseFuroContext<null, FuroEditorContextParams['props'], string>}
|
|
90
|
+
*/
|
|
91
|
+
export default class FuroEditorContext extends BaseFuroContext {
|
|
92
|
+
/**
|
|
93
|
+
* Constructor.
|
|
94
|
+
*
|
|
95
|
+
* @param {FuroEditorContextParams} params - Parameters of this constructor.
|
|
96
|
+
*/
|
|
97
|
+
constructor ({
|
|
98
|
+
props,
|
|
99
|
+
componentContext,
|
|
100
|
+
|
|
101
|
+
reactiveHash,
|
|
102
|
+
editorRef,
|
|
103
|
+
}) {
|
|
104
|
+
super({
|
|
105
|
+
props,
|
|
106
|
+
componentContext,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
this.reactiveHash = reactiveHash
|
|
110
|
+
this.editorRef = editorRef
|
|
111
|
+
this.commandContext = FuroEditorCommandContext.create({
|
|
112
|
+
editorRef,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
/** @type {{ from: number, to: number } | null} */
|
|
116
|
+
this.linkRange = null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Factory method to create a new instance of this class.
|
|
121
|
+
*
|
|
122
|
+
* @template {X extends typeof FuroEditorContext ? X : never} T, X
|
|
123
|
+
* @override
|
|
124
|
+
* @param {FuroEditorContextFactoryParams} params - Parameters of this factory method.
|
|
125
|
+
* @returns {InstanceType<T>} - New instance of this class.
|
|
126
|
+
* @this {T}
|
|
127
|
+
*/
|
|
128
|
+
static create ({
|
|
129
|
+
props,
|
|
130
|
+
componentContext,
|
|
131
|
+
|
|
132
|
+
editorRef,
|
|
133
|
+
reactiveHash = this.buildReactiveHash(),
|
|
134
|
+
}) {
|
|
135
|
+
return /** @type {InstanceType<T>} */ (
|
|
136
|
+
new this({
|
|
137
|
+
props,
|
|
138
|
+
componentContext,
|
|
139
|
+
|
|
140
|
+
reactiveHash,
|
|
141
|
+
editorRef,
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @override */
|
|
147
|
+
static get EMIT_EVENT_NAME () {
|
|
148
|
+
return {
|
|
149
|
+
UPDATE_VALUE: 'update:value',
|
|
150
|
+
CHANGE_VALUE: 'change-value',
|
|
151
|
+
COMMIT_VALUE: 'commit-value',
|
|
152
|
+
UPDATE_ATTACHED_FILES: 'update:attachedFiles',
|
|
153
|
+
ADD_MENTION: 'mention:add',
|
|
154
|
+
ADD_FILES: 'files:add',
|
|
155
|
+
IMAGE_ERROR: 'image:error',
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build reactive hash.
|
|
161
|
+
*
|
|
162
|
+
* @override
|
|
163
|
+
* @returns {FuroEditorContextParams['reactiveHash']}
|
|
164
|
+
*/
|
|
165
|
+
static buildReactiveHash () {
|
|
166
|
+
return {
|
|
167
|
+
statusReactive: reactive({
|
|
168
|
+
isLinkDialogOpen: false,
|
|
169
|
+
}),
|
|
170
|
+
addLinkReactive: reactive({
|
|
171
|
+
url: '',
|
|
172
|
+
displayText: '',
|
|
173
|
+
}),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* get: default toolbar labels.
|
|
179
|
+
*
|
|
180
|
+
* @returns {typeof DEFAULT_LABELS} - Default labels.
|
|
181
|
+
*/
|
|
182
|
+
static get DEFAULT_LABELS () {
|
|
183
|
+
return DEFAULT_LABELS
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Build the option object for the `useEditor()` composable.
|
|
188
|
+
*
|
|
189
|
+
* Keeps all editor-assembly logic out of the component's `setup()`: the SFC only
|
|
190
|
+
* passes the result to `useEditor()` and hands the ref to {@link create}.
|
|
191
|
+
*
|
|
192
|
+
* The Tiptap builders are injected (not imported) so this context module — and the
|
|
193
|
+
* unit tests that load it — stay free of the editor runtime.
|
|
194
|
+
*
|
|
195
|
+
* @param {{
|
|
196
|
+
* props: { parcel?: FuroEditorParcel | null }
|
|
197
|
+
* componentContext: import('vue').SetupContext<*>
|
|
198
|
+
* buildExtensions: typeof import('./build-editor-extensions.js').buildEditorExtensions
|
|
199
|
+
* createMention: typeof import('./mention-suggestion.js').createMentionExtension
|
|
200
|
+
* }} params - Parameters of this method.
|
|
201
|
+
* @returns {Partial<import('@tiptap/core').EditorOptions>} - useEditor() options.
|
|
202
|
+
*/
|
|
203
|
+
static buildEditorOptions ({
|
|
204
|
+
props,
|
|
205
|
+
componentContext,
|
|
206
|
+
buildExtensions,
|
|
207
|
+
createMention,
|
|
208
|
+
}) {
|
|
209
|
+
const parcel = props.parcel
|
|
210
|
+
?? {}
|
|
211
|
+
|
|
212
|
+
const mentionExtension = this.buildMentionExtension({
|
|
213
|
+
parcel,
|
|
214
|
+
componentContext,
|
|
215
|
+
createMention,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const placeholder = parcel.placeholder
|
|
219
|
+
?? ''
|
|
220
|
+
const content = parcel.value
|
|
221
|
+
?? ''
|
|
222
|
+
|
|
223
|
+
const extensions = buildExtensions({
|
|
224
|
+
placeholder,
|
|
225
|
+
enableImage: Boolean(parcel.image?.uploadImage),
|
|
226
|
+
mentionExtension,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const attributes = parcel.contentAttributes
|
|
230
|
+
?? {}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
editable: !parcel.readOnly && parcel.appearance !== 'plain',
|
|
234
|
+
content,
|
|
235
|
+
autofocus: parcel.autofocus ?? false,
|
|
236
|
+
extensions,
|
|
237
|
+
editorProps: {
|
|
238
|
+
attributes,
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Build the mention extension via the injected factory.
|
|
245
|
+
*
|
|
246
|
+
* Always registers the mention node so saved chips render in any editor (including
|
|
247
|
+
* read-only / display). The live `@` popup is wired only when a search adapter is
|
|
248
|
+
* supplied.
|
|
249
|
+
*
|
|
250
|
+
* @param {{
|
|
251
|
+
* parcel: FuroEditorParcel
|
|
252
|
+
* componentContext: import('vue').SetupContext<*>
|
|
253
|
+
* createMention: typeof import('./mention-suggestion.js').createMentionExtension
|
|
254
|
+
* }} params - Parameters of this method.
|
|
255
|
+
* @returns {import('@tiptap/core').AnyExtension} - Mention extension.
|
|
256
|
+
*/
|
|
257
|
+
static buildMentionExtension ({
|
|
258
|
+
parcel,
|
|
259
|
+
componentContext,
|
|
260
|
+
createMention,
|
|
261
|
+
}) {
|
|
262
|
+
const searchMentions = parcel.mention?.searchMentions
|
|
263
|
+
|
|
264
|
+
if (!searchMentions) {
|
|
265
|
+
return createMention()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return createMention({
|
|
269
|
+
searchMentions,
|
|
270
|
+
onMentionAdd: mention => componentContext.emit(
|
|
271
|
+
this.EMIT_EVENT_NAME.ADD_MENTION,
|
|
272
|
+
mention
|
|
273
|
+
),
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* get: live editor instance, or null before it mounts.
|
|
279
|
+
*
|
|
280
|
+
* @returns {import('@tiptap/vue-3').Editor | null} - Editor instance.
|
|
281
|
+
*/
|
|
282
|
+
get editor () {
|
|
283
|
+
return this.editorRef.value
|
|
284
|
+
?? null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* get: the command sub-context.
|
|
289
|
+
*
|
|
290
|
+
* @returns {FuroEditorCommandContext} - Command facade.
|
|
291
|
+
*/
|
|
292
|
+
get command () {
|
|
293
|
+
return this.commandContext
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* get: status reactive (link dialog open state).
|
|
298
|
+
*
|
|
299
|
+
* @returns {{ isLinkDialogOpen: boolean }} - Status reactive.
|
|
300
|
+
*/
|
|
301
|
+
get statusReactive () {
|
|
302
|
+
return this.reactiveHash.statusReactive
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* get: add-link reactive (url + display text).
|
|
307
|
+
*
|
|
308
|
+
* @returns {{ url: string, displayText: string }} - Link reactive.
|
|
309
|
+
*/
|
|
310
|
+
get addLinkReactive () {
|
|
311
|
+
return this.reactiveHash.addLinkReactive
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* get: current HTML value from parcel.
|
|
316
|
+
*
|
|
317
|
+
* @returns {string} - HTML content.
|
|
318
|
+
*/
|
|
319
|
+
get value () {
|
|
320
|
+
return this.parcel?.value
|
|
321
|
+
?? ''
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* get: placeholder text.
|
|
326
|
+
*
|
|
327
|
+
* @returns {string} - Placeholder.
|
|
328
|
+
*/
|
|
329
|
+
get placeholder () {
|
|
330
|
+
return this.parcel?.placeholder
|
|
331
|
+
?? ''
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* get: attached files passed in by the consumer.
|
|
336
|
+
*
|
|
337
|
+
* @returns {Array<*>} - Attached files.
|
|
338
|
+
*/
|
|
339
|
+
get attachedFiles () {
|
|
340
|
+
return this.parcel?.attachedFiles
|
|
341
|
+
?? []
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Whether the editor is read-only.
|
|
346
|
+
*
|
|
347
|
+
* @returns {boolean} - true when read-only.
|
|
348
|
+
*/
|
|
349
|
+
isReadOnly () {
|
|
350
|
+
return Boolean(this.parcel?.readOnly)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Whether the editor renders as plain text (no chrome: toolbar, border, padding).
|
|
355
|
+
*
|
|
356
|
+
* @returns {boolean} - true in plain appearance.
|
|
357
|
+
*/
|
|
358
|
+
isPlain () {
|
|
359
|
+
return this.parcel?.appearance === 'plain'
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Whether the editor is display-only (read-only or plain) — never editable.
|
|
364
|
+
*
|
|
365
|
+
* @returns {boolean} - true when display-only.
|
|
366
|
+
*/
|
|
367
|
+
isDisplayOnly () {
|
|
368
|
+
return this.isReadOnly()
|
|
369
|
+
|| this.isPlain()
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Whether the editor instance is currently editable.
|
|
374
|
+
*
|
|
375
|
+
* @returns {boolean} - true when editable.
|
|
376
|
+
*/
|
|
377
|
+
isEditorEditable () {
|
|
378
|
+
return this.editor?.isEditable
|
|
379
|
+
?? false
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Whether the mention feature is enabled (a search adapter was supplied).
|
|
384
|
+
*
|
|
385
|
+
* @returns {boolean} - true when mentions are available.
|
|
386
|
+
*/
|
|
387
|
+
hasMentionFeature () {
|
|
388
|
+
return Boolean(this.parcel?.mention?.searchMentions)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Whether inline images are enabled (an upload adapter was supplied).
|
|
393
|
+
*
|
|
394
|
+
* @returns {boolean} - true when inline images are available.
|
|
395
|
+
*/
|
|
396
|
+
hasImageFeature () {
|
|
397
|
+
return Boolean(this.parcel?.image?.uploadImage)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Whether the link dialog is open.
|
|
402
|
+
*
|
|
403
|
+
* @returns {boolean} - true when open.
|
|
404
|
+
*/
|
|
405
|
+
isLinkDialogOpen () {
|
|
406
|
+
return this.statusReactive.isLinkDialogOpen
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Merge consumer label overrides over the defaults.
|
|
411
|
+
*
|
|
412
|
+
* @returns {typeof DEFAULT_LABELS} - Effective labels.
|
|
413
|
+
*/
|
|
414
|
+
extractLabels () {
|
|
415
|
+
return {
|
|
416
|
+
...DEFAULT_LABELS,
|
|
417
|
+
...this.parcel?.labels,
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Setup component context: bind editor events, paste handlers, and value/readonly sync.
|
|
423
|
+
*
|
|
424
|
+
* @template {X extends FuroEditorContext ? X : never} T, X
|
|
425
|
+
* @override
|
|
426
|
+
* @returns {T} - For method chaining.
|
|
427
|
+
* @this {T}
|
|
428
|
+
*/
|
|
429
|
+
setupComponent () {
|
|
430
|
+
onMounted(() => {
|
|
431
|
+
this.bindEditorEvents()
|
|
432
|
+
this.bindPasteHandlers()
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
this.watch(
|
|
436
|
+
() => this.value,
|
|
437
|
+
() => this.syncValueIntoEditor(),
|
|
438
|
+
{
|
|
439
|
+
immediate: true,
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
this.watch(
|
|
444
|
+
() => this.isDisplayOnly(),
|
|
445
|
+
() => this.syncEditableState(),
|
|
446
|
+
{
|
|
447
|
+
immediate: true,
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
// useEditor() from @tiptap/vue-3 destroys the editor on unmount automatically.
|
|
452
|
+
|
|
453
|
+
return this
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Bind editor update/blur events to emits.
|
|
458
|
+
*
|
|
459
|
+
* @returns {void}
|
|
460
|
+
*/
|
|
461
|
+
bindEditorEvents () {
|
|
462
|
+
if (!this.editor) {
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
this.editor.on('update', ({
|
|
467
|
+
editor,
|
|
468
|
+
}) => {
|
|
469
|
+
const html = editor.getHTML()
|
|
470
|
+
|
|
471
|
+
this.emit(this.EMIT_EVENT_NAME.UPDATE_VALUE, html)
|
|
472
|
+
this.emit(this.EMIT_EVENT_NAME.CHANGE_VALUE, {
|
|
473
|
+
value: html,
|
|
474
|
+
})
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
this.editor.on('blur', ({
|
|
478
|
+
editor,
|
|
479
|
+
}) => {
|
|
480
|
+
this.emit(this.EMIT_EVENT_NAME.COMMIT_VALUE, {
|
|
481
|
+
value: editor.getHTML(),
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Install paste transforms (markdown detection, font-style stripping, normalization).
|
|
488
|
+
*
|
|
489
|
+
* @returns {void}
|
|
490
|
+
*/
|
|
491
|
+
bindPasteHandlers () {
|
|
492
|
+
if (!this.editor) {
|
|
493
|
+
return
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
this.editor.setOptions({
|
|
497
|
+
editorProps: {
|
|
498
|
+
...this.editor.options.editorProps,
|
|
499
|
+
transformPastedText: text => this.normalizePastedText({
|
|
500
|
+
text,
|
|
501
|
+
}),
|
|
502
|
+
transformPastedHTML: html => this.stripPastedHtmlFontStyles({
|
|
503
|
+
html,
|
|
504
|
+
}),
|
|
505
|
+
handlePaste: (_, clipboardEvent) => this.handleEditorPaste({
|
|
506
|
+
clipboardEvent,
|
|
507
|
+
}),
|
|
508
|
+
handleDrop: (view, dragEvent) => this.handleEditorDrop({
|
|
509
|
+
view,
|
|
510
|
+
dragEvent,
|
|
511
|
+
}),
|
|
512
|
+
},
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Handle a paste: inline images first, then markdown plain text.
|
|
518
|
+
*
|
|
519
|
+
* @param {{
|
|
520
|
+
* clipboardEvent: ClipboardEvent
|
|
521
|
+
* }} params - Parameters of this method.
|
|
522
|
+
* @returns {boolean} - true when a handler consumed the paste.
|
|
523
|
+
*/
|
|
524
|
+
handleEditorPaste ({
|
|
525
|
+
clipboardEvent,
|
|
526
|
+
}) {
|
|
527
|
+
this.emitPastedFiles({ clipboardEvent })
|
|
528
|
+
|
|
529
|
+
if (this.handleImagePaste({ clipboardEvent })) {
|
|
530
|
+
return true
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return this.handleMarkdownPaste({ clipboardEvent })
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Emit the pasted files the editor does not insert itself.
|
|
538
|
+
*
|
|
539
|
+
* @param {{
|
|
540
|
+
* clipboardEvent: ClipboardEvent
|
|
541
|
+
* }} params - Parameters of this method.
|
|
542
|
+
* @returns {void}
|
|
543
|
+
*/
|
|
544
|
+
emitPastedFiles ({
|
|
545
|
+
clipboardEvent,
|
|
546
|
+
}) {
|
|
547
|
+
const clipboard = clipboardEvent?.['clipboardData']
|
|
548
|
+
const files = this.extractUnconsumedFiles({
|
|
549
|
+
fileSource: clipboard?.files ?? null,
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
this.emitAddedFiles({ files })
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Emit files handed to the consumer, unless there are none.
|
|
557
|
+
*
|
|
558
|
+
* @param {{
|
|
559
|
+
* files: Array<File>
|
|
560
|
+
* }} params - Parameters of this method.
|
|
561
|
+
* @returns {void}
|
|
562
|
+
*/
|
|
563
|
+
emitAddedFiles ({
|
|
564
|
+
files,
|
|
565
|
+
}) {
|
|
566
|
+
if (files.length === 0) {
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this.emit(this.EMIT_EVENT_NAME.ADD_FILES, {
|
|
571
|
+
files,
|
|
572
|
+
})
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Upload and insert any image files present on the clipboard.
|
|
577
|
+
*
|
|
578
|
+
* @param {{
|
|
579
|
+
* clipboardEvent: ClipboardEvent
|
|
580
|
+
* }} params - Parameters of this method.
|
|
581
|
+
* @returns {boolean} - true when image files were consumed.
|
|
582
|
+
*/
|
|
583
|
+
handleImagePaste ({
|
|
584
|
+
clipboardEvent,
|
|
585
|
+
}) {
|
|
586
|
+
if (!this.hasImageFeature()) {
|
|
587
|
+
return false
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const clipboard = clipboardEvent?.['clipboardData']
|
|
591
|
+
const imageFiles = this.extractImageFiles({
|
|
592
|
+
fileSource: clipboard?.files ?? null,
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
if (imageFiles.length === 0) {
|
|
596
|
+
return false
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
this.uploadAndInsertImages({
|
|
600
|
+
files: imageFiles,
|
|
601
|
+
position: null,
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
return true
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Insert dropped images at the drop position, and hand the other files to the consumer.
|
|
609
|
+
*
|
|
610
|
+
* @param {{
|
|
611
|
+
* view: import('@tiptap/pm/view').EditorView
|
|
612
|
+
* dragEvent: DragEvent
|
|
613
|
+
* }} params - Parameters of this method.
|
|
614
|
+
* @returns {boolean} - true when the dropped files were taken.
|
|
615
|
+
*/
|
|
616
|
+
handleEditorDrop ({
|
|
617
|
+
view,
|
|
618
|
+
dragEvent,
|
|
619
|
+
}) {
|
|
620
|
+
const fileSource = dragEvent?.dataTransfer?.files
|
|
621
|
+
?? null
|
|
622
|
+
const imageFiles = this.extractInsertableImageFiles({ fileSource })
|
|
623
|
+
const droppedFiles = this.extractUnconsumedFiles({ fileSource })
|
|
624
|
+
|
|
625
|
+
if (imageFiles.length === 0 && droppedFiles.length === 0) {
|
|
626
|
+
return false
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
dragEvent.preventDefault()
|
|
630
|
+
|
|
631
|
+
this.emitAddedFiles({ files: droppedFiles })
|
|
632
|
+
|
|
633
|
+
const position = this.extractDropPosition({
|
|
634
|
+
view,
|
|
635
|
+
dragEvent,
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
this.uploadAndInsertImages({
|
|
639
|
+
files: imageFiles,
|
|
640
|
+
position,
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
return true
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Extract the document position a drag event points at.
|
|
648
|
+
*
|
|
649
|
+
* @param {{
|
|
650
|
+
* view: import('@tiptap/pm/view').EditorView
|
|
651
|
+
* dragEvent: DragEvent
|
|
652
|
+
* }} params - Parameters of this method.
|
|
653
|
+
* @returns {number | null} - Document position.
|
|
654
|
+
*/
|
|
655
|
+
extractDropPosition ({
|
|
656
|
+
view,
|
|
657
|
+
dragEvent,
|
|
658
|
+
}) {
|
|
659
|
+
const coordinates = view.posAtCoords({
|
|
660
|
+
left: dragEvent.clientX,
|
|
661
|
+
top: dragEvent.clientY,
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
return coordinates?.pos
|
|
665
|
+
?? null
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Push the parcel value into the editor when it diverges from the current HTML.
|
|
670
|
+
*
|
|
671
|
+
* @returns {void}
|
|
672
|
+
*/
|
|
673
|
+
syncValueIntoEditor () {
|
|
674
|
+
if (!this.editor) {
|
|
675
|
+
return
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const currentHtml = this.editor.getHTML()
|
|
679
|
+
|
|
680
|
+
if (this.value === currentHtml) {
|
|
681
|
+
return
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
this.editor
|
|
685
|
+
.commands
|
|
686
|
+
.setContent(this.value)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Reflect the read-only flag onto the editor.
|
|
691
|
+
*
|
|
692
|
+
* @returns {void}
|
|
693
|
+
*/
|
|
694
|
+
syncEditableState () {
|
|
695
|
+
if (!this.editor) {
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
this.editor.setEditable(!this.isDisplayOnly())
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Convert pasted markdown plain text into HTML and insert it.
|
|
704
|
+
*
|
|
705
|
+
* @param {{
|
|
706
|
+
* clipboardEvent: ClipboardEvent
|
|
707
|
+
* }} params - Parameters of this method.
|
|
708
|
+
* @returns {boolean} - true when this handler consumed the paste.
|
|
709
|
+
*/
|
|
710
|
+
handleMarkdownPaste ({
|
|
711
|
+
clipboardEvent,
|
|
712
|
+
}) {
|
|
713
|
+
// Bracket access avoids the Data-suffix identifier lint on native ClipboardEvent APIs.
|
|
714
|
+
const clipboard = clipboardEvent?.['clipboardData']
|
|
715
|
+
const rawText = clipboard?.['getData']?.('text/plain')
|
|
716
|
+
|
|
717
|
+
if (!rawText) {
|
|
718
|
+
return false
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (!this.isMarkdown({ text: rawText })) {
|
|
722
|
+
return false
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
this.insertMarkdownAsHtml({
|
|
726
|
+
rawText,
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
return true
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Render markdown to HTML asynchronously and insert it at the cursor.
|
|
734
|
+
*
|
|
735
|
+
* @param {{
|
|
736
|
+
* rawText: string
|
|
737
|
+
* }} params - Parameters of this method.
|
|
738
|
+
* @returns {void}
|
|
739
|
+
*/
|
|
740
|
+
insertMarkdownAsHtml ({
|
|
741
|
+
rawText,
|
|
742
|
+
}) {
|
|
743
|
+
Promise.resolve(marked(rawText))
|
|
744
|
+
.then(html => {
|
|
745
|
+
this.editor
|
|
746
|
+
?.commands
|
|
747
|
+
.insertContent(html)
|
|
748
|
+
})
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Normalize pasted plain-text line breaks.
|
|
753
|
+
*
|
|
754
|
+
* @param {{
|
|
755
|
+
* text: string
|
|
756
|
+
* }} params - Parameters of this method.
|
|
757
|
+
* @returns {string} - Normalized text.
|
|
758
|
+
*/
|
|
759
|
+
normalizePastedText ({
|
|
760
|
+
text,
|
|
761
|
+
}) {
|
|
762
|
+
return text
|
|
763
|
+
.replace(/\r\n/gu, '\n')
|
|
764
|
+
.replace(/\r/gu, '\n')
|
|
765
|
+
.replace(/\n{3,}/gu, '\n\n')
|
|
766
|
+
.trim()
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Strip font-family / font-size declarations from pasted HTML so external
|
|
771
|
+
* typography does not override the editor's.
|
|
772
|
+
*
|
|
773
|
+
* @param {{
|
|
774
|
+
* html: string
|
|
775
|
+
* }} params - Parameters of this method.
|
|
776
|
+
* @returns {string} - Sanitized HTML.
|
|
777
|
+
*/
|
|
778
|
+
stripPastedHtmlFontStyles ({
|
|
779
|
+
html,
|
|
780
|
+
}) {
|
|
781
|
+
return html
|
|
782
|
+
.replace(/font-family:\s*[^;"]+;?/giu, '')
|
|
783
|
+
.replace(/font-size:\s*[^;"]+;?/giu, '')
|
|
784
|
+
.replace(/\s*style="\s*"/giu, '')
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Whether pasted text looks like markdown.
|
|
789
|
+
*
|
|
790
|
+
* @param {{
|
|
791
|
+
* text: string
|
|
792
|
+
* }} params - Parameters of this method.
|
|
793
|
+
* @returns {boolean} - true when markdown patterns match.
|
|
794
|
+
*/
|
|
795
|
+
isMarkdown ({
|
|
796
|
+
text,
|
|
797
|
+
}) {
|
|
798
|
+
return MARKDOWN_PATTERNS.some(pattern => pattern.test(text))
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Build the inline-mark controls (bold / italic / underline / strikethrough).
|
|
803
|
+
*
|
|
804
|
+
* @returns {Array<FuroEditorControl>} - Mark controls.
|
|
805
|
+
*/
|
|
806
|
+
generateMarkControls () {
|
|
807
|
+
const labels = this.extractLabels()
|
|
808
|
+
|
|
809
|
+
return /** @type {Array<FuroEditorControl>} */ ([
|
|
810
|
+
{
|
|
811
|
+
name: 'bold',
|
|
812
|
+
kind: 'toggle',
|
|
813
|
+
command: 'toggleBold',
|
|
814
|
+
iconName: 'ph:text-b',
|
|
815
|
+
activeIconName: 'ph:text-b-bold',
|
|
816
|
+
tooltip: labels.bold,
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
name: 'italic',
|
|
820
|
+
kind: 'toggle',
|
|
821
|
+
command: 'toggleItalic',
|
|
822
|
+
iconName: 'ph:text-italic',
|
|
823
|
+
activeIconName: 'ph:text-italic-bold',
|
|
824
|
+
tooltip: labels.italic,
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
name: 'underline',
|
|
828
|
+
kind: 'toggle',
|
|
829
|
+
command: 'toggleUnderline',
|
|
830
|
+
iconName: 'ph:text-underline',
|
|
831
|
+
activeIconName: 'ph:text-underline-bold',
|
|
832
|
+
tooltip: labels.underline,
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: 'strike',
|
|
836
|
+
kind: 'toggle',
|
|
837
|
+
command: 'toggleStrike',
|
|
838
|
+
iconName: 'ph:text-strikethrough',
|
|
839
|
+
activeIconName: 'ph:text-strikethrough-bold',
|
|
840
|
+
tooltip: labels.strike,
|
|
841
|
+
},
|
|
842
|
+
])
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Build the block controls (bullets / numbers / blockquote).
|
|
847
|
+
*
|
|
848
|
+
* @returns {Array<FuroEditorControl>} - Block controls.
|
|
849
|
+
*/
|
|
850
|
+
generateBlockControls () {
|
|
851
|
+
const labels = this.extractLabels()
|
|
852
|
+
|
|
853
|
+
return /** @type {Array<FuroEditorControl>} */ ([
|
|
854
|
+
{
|
|
855
|
+
name: 'bulletList',
|
|
856
|
+
kind: 'toggle',
|
|
857
|
+
command: 'toggleBulletList',
|
|
858
|
+
iconName: 'ph:list-bullets',
|
|
859
|
+
activeIconName: 'ph:list-bullets-bold',
|
|
860
|
+
tooltip: labels.bullets,
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
name: 'orderedList',
|
|
864
|
+
kind: 'toggle',
|
|
865
|
+
command: 'toggleOrderedList',
|
|
866
|
+
iconName: 'ph:list-numbers',
|
|
867
|
+
activeIconName: 'ph:list-numbers-bold',
|
|
868
|
+
tooltip: labels.numbers,
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: 'blockquote',
|
|
872
|
+
kind: 'toggle',
|
|
873
|
+
command: 'toggleBlockquote',
|
|
874
|
+
iconName: 'ph:quotes',
|
|
875
|
+
activeIconName: 'ph:quotes-bold',
|
|
876
|
+
tooltip: labels.blockquote,
|
|
877
|
+
},
|
|
878
|
+
])
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Build the action controls (link, plus image / mention when enabled).
|
|
883
|
+
*
|
|
884
|
+
* @returns {Array<FuroEditorControl>} - Action controls.
|
|
885
|
+
*/
|
|
886
|
+
generateActionControls () {
|
|
887
|
+
const labels = this.extractLabels()
|
|
888
|
+
|
|
889
|
+
const linkControls = [
|
|
890
|
+
{
|
|
891
|
+
name: 'link',
|
|
892
|
+
kind: 'link',
|
|
893
|
+
command: '',
|
|
894
|
+
iconName: 'ph:link',
|
|
895
|
+
activeIconName: 'ph:link-bold',
|
|
896
|
+
tooltip: labels.link,
|
|
897
|
+
},
|
|
898
|
+
]
|
|
899
|
+
|
|
900
|
+
const imageControls = this.hasImageFeature()
|
|
901
|
+
? [
|
|
902
|
+
{
|
|
903
|
+
name: 'image',
|
|
904
|
+
kind: 'image',
|
|
905
|
+
command: '',
|
|
906
|
+
iconName: 'ph:image',
|
|
907
|
+
activeIconName: 'ph:image-fill',
|
|
908
|
+
tooltip: labels.image,
|
|
909
|
+
},
|
|
910
|
+
]
|
|
911
|
+
: []
|
|
912
|
+
|
|
913
|
+
const mentionControls = this.hasMentionFeature()
|
|
914
|
+
? [
|
|
915
|
+
{
|
|
916
|
+
name: 'mention',
|
|
917
|
+
kind: 'mention',
|
|
918
|
+
command: '',
|
|
919
|
+
iconName: 'ph:at',
|
|
920
|
+
activeIconName: 'ph:at-bold',
|
|
921
|
+
tooltip: labels.mention,
|
|
922
|
+
},
|
|
923
|
+
]
|
|
924
|
+
: []
|
|
925
|
+
|
|
926
|
+
return /** @type {Array<FuroEditorControl>} */ ([
|
|
927
|
+
...linkControls,
|
|
928
|
+
...imageControls,
|
|
929
|
+
...mentionControls,
|
|
930
|
+
])
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Flatten all toolbar controls into a single list.
|
|
935
|
+
*
|
|
936
|
+
* @returns {Array<FuroEditorControl>} - Toolbar controls.
|
|
937
|
+
*/
|
|
938
|
+
generateFormattingControls () {
|
|
939
|
+
return [
|
|
940
|
+
...this.generateMarkControls(),
|
|
941
|
+
...this.generateBlockControls(),
|
|
942
|
+
...this.generateActionControls(),
|
|
943
|
+
]
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Build a lookup of control descriptors keyed by control name.
|
|
948
|
+
*
|
|
949
|
+
* @returns {Record<string, FuroEditorControl>} - Controls keyed by name.
|
|
950
|
+
*/
|
|
951
|
+
generateControlHash () {
|
|
952
|
+
const entries = this.generateFormattingControls()
|
|
953
|
+
.map(control => /** @type {[string, FuroEditorControl]} */ ([
|
|
954
|
+
control.name,
|
|
955
|
+
control,
|
|
956
|
+
]))
|
|
957
|
+
|
|
958
|
+
return Object.fromEntries(entries)
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Build a lookup of dropdown descriptors keyed by name.
|
|
963
|
+
*
|
|
964
|
+
* @returns {Record<string, FuroEditorToolbarEntry>} - Dropdown entries keyed by name.
|
|
965
|
+
*/
|
|
966
|
+
generateDropdownDescriptorHash () {
|
|
967
|
+
const labels = this.extractLabels()
|
|
968
|
+
|
|
969
|
+
return /** @type {Record<string, FuroEditorToolbarEntry>} */ ({
|
|
970
|
+
heading: {
|
|
971
|
+
type: 'dropdown',
|
|
972
|
+
key: 'heading',
|
|
973
|
+
label: labels.heading,
|
|
974
|
+
iconName: 'ph:text-aa',
|
|
975
|
+
dropdownKind: 'heading',
|
|
976
|
+
options: this.generateHeadingOptions(),
|
|
977
|
+
},
|
|
978
|
+
color: {
|
|
979
|
+
type: 'dropdown',
|
|
980
|
+
key: 'color',
|
|
981
|
+
label: labels.textColor,
|
|
982
|
+
iconName: 'ph:text-t',
|
|
983
|
+
dropdownKind: 'textStyle',
|
|
984
|
+
hasSwatch: true,
|
|
985
|
+
options: this.generateColorOptions(),
|
|
986
|
+
},
|
|
987
|
+
align: {
|
|
988
|
+
type: 'dropdown',
|
|
989
|
+
key: 'align',
|
|
990
|
+
label: labels.textAlign,
|
|
991
|
+
iconName: 'ph:text-align-left',
|
|
992
|
+
dropdownKind: 'textAlign',
|
|
993
|
+
options: this.generateAlignOptions(),
|
|
994
|
+
},
|
|
995
|
+
})
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* The default toolbar layout: an array of groups, separators sit between groups.
|
|
1000
|
+
*
|
|
1001
|
+
* @returns {Array<Array<string>>} - Groups of item names.
|
|
1002
|
+
*/
|
|
1003
|
+
generateDefaultToolbarGroups () {
|
|
1004
|
+
const imageActions = this.hasImageFeature()
|
|
1005
|
+
? [
|
|
1006
|
+
'image',
|
|
1007
|
+
]
|
|
1008
|
+
: []
|
|
1009
|
+
|
|
1010
|
+
const mentionActions = this.hasMentionFeature()
|
|
1011
|
+
? [
|
|
1012
|
+
'mention',
|
|
1013
|
+
]
|
|
1014
|
+
: []
|
|
1015
|
+
|
|
1016
|
+
const actionGroup = [
|
|
1017
|
+
'link',
|
|
1018
|
+
...imageActions,
|
|
1019
|
+
...mentionActions,
|
|
1020
|
+
]
|
|
1021
|
+
|
|
1022
|
+
return [
|
|
1023
|
+
[
|
|
1024
|
+
'heading',
|
|
1025
|
+
'color',
|
|
1026
|
+
'align',
|
|
1027
|
+
],
|
|
1028
|
+
[
|
|
1029
|
+
'bold',
|
|
1030
|
+
'italic',
|
|
1031
|
+
'underline',
|
|
1032
|
+
'strike',
|
|
1033
|
+
],
|
|
1034
|
+
[
|
|
1035
|
+
'bulletList',
|
|
1036
|
+
'orderedList',
|
|
1037
|
+
'blockquote',
|
|
1038
|
+
],
|
|
1039
|
+
actionGroup,
|
|
1040
|
+
]
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* The effective toolbar groups (consumer override or the default).
|
|
1045
|
+
*
|
|
1046
|
+
* @returns {Array<Array<string>>} - Groups of item names.
|
|
1047
|
+
*/
|
|
1048
|
+
generateToolbarGroups () {
|
|
1049
|
+
return this.parcel?.toolbar
|
|
1050
|
+
?? this.generateDefaultToolbarGroups()
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Resolve a name to a dropdown or control descriptor, leaving `overflow` unhandled.
|
|
1055
|
+
*
|
|
1056
|
+
* @param {{
|
|
1057
|
+
* name: string
|
|
1058
|
+
* dropdownHash: Record<string, FuroEditorToolbarEntry>
|
|
1059
|
+
* controlHash: Record<string, FuroEditorControl>
|
|
1060
|
+
* }} params - Parameters of this method.
|
|
1061
|
+
* @returns {FuroEditorToolbarEntry | null} - Render descriptor.
|
|
1062
|
+
*/
|
|
1063
|
+
resolveDropdownOrControlEntry ({
|
|
1064
|
+
name,
|
|
1065
|
+
dropdownHash,
|
|
1066
|
+
controlHash,
|
|
1067
|
+
}) {
|
|
1068
|
+
const dropdownEntry = dropdownHash[name]
|
|
1069
|
+
|
|
1070
|
+
if (dropdownEntry) {
|
|
1071
|
+
return dropdownEntry
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const control = controlHash[name]
|
|
1075
|
+
|
|
1076
|
+
if (!control) {
|
|
1077
|
+
return null
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
return /** @type {FuroEditorToolbarEntry} */ ({
|
|
1081
|
+
type: 'control',
|
|
1082
|
+
key: name,
|
|
1083
|
+
control,
|
|
1084
|
+
})
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Resolve a toolbar item name to a render descriptor, or null when unavailable.
|
|
1089
|
+
*
|
|
1090
|
+
* @param {{
|
|
1091
|
+
* name: string
|
|
1092
|
+
* dropdownHash: Record<string, FuroEditorToolbarEntry>
|
|
1093
|
+
* controlHash: Record<string, FuroEditorControl>
|
|
1094
|
+
* }} params - Parameters of this method.
|
|
1095
|
+
* @returns {FuroEditorToolbarEntry | null} - Render descriptor.
|
|
1096
|
+
*/
|
|
1097
|
+
resolveToolbarEntry ({
|
|
1098
|
+
name,
|
|
1099
|
+
dropdownHash,
|
|
1100
|
+
controlHash,
|
|
1101
|
+
}) {
|
|
1102
|
+
const entry = this.resolveDropdownOrControlEntry({
|
|
1103
|
+
name,
|
|
1104
|
+
dropdownHash,
|
|
1105
|
+
controlHash,
|
|
1106
|
+
})
|
|
1107
|
+
|
|
1108
|
+
if (entry) {
|
|
1109
|
+
return entry
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
if (name === OVERFLOW_TOOLBAR_NAME) {
|
|
1113
|
+
return this.resolveOverflowEntry()
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
this.throwOnUnknownToolbarName({
|
|
1117
|
+
name,
|
|
1118
|
+
})
|
|
1119
|
+
|
|
1120
|
+
return null
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Resolve the overflow menu, or null when no action is left to put in it.
|
|
1125
|
+
*
|
|
1126
|
+
* @returns {FuroEditorToolbarEntry | null} - Render descriptor.
|
|
1127
|
+
*/
|
|
1128
|
+
resolveOverflowEntry () {
|
|
1129
|
+
if (this.generateOverflowEntryNames().length === 0) {
|
|
1130
|
+
return null
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
return /** @type {FuroEditorToolbarEntry} */ ({
|
|
1134
|
+
type: 'overflow',
|
|
1135
|
+
key: OVERFLOW_TOOLBAR_NAME,
|
|
1136
|
+
})
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* List the toolbar names this editor accepts, feature-gated names included.
|
|
1141
|
+
*
|
|
1142
|
+
* @returns {Array<string>} - Accepted names.
|
|
1143
|
+
*/
|
|
1144
|
+
generateKnownToolbarNames () {
|
|
1145
|
+
const controlNames = this.generateFormattingControls()
|
|
1146
|
+
.map(control => control.name)
|
|
1147
|
+
|
|
1148
|
+
const gatedNames = FEATURE_TOOLBAR_NAMES
|
|
1149
|
+
.filter(name => !controlNames.includes(name))
|
|
1150
|
+
|
|
1151
|
+
return [
|
|
1152
|
+
...Object.keys(this.generateDropdownDescriptorHash()),
|
|
1153
|
+
...controlNames,
|
|
1154
|
+
...gatedNames,
|
|
1155
|
+
OVERFLOW_TOOLBAR_NAME,
|
|
1156
|
+
]
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Whether a toolbar name lies outside the vocabulary. A gated feature's own name
|
|
1161
|
+
* stays known, so switching the feature off never reads as a typo.
|
|
1162
|
+
*
|
|
1163
|
+
* @param {{
|
|
1164
|
+
* name: string
|
|
1165
|
+
* }} params - Parameters of this method.
|
|
1166
|
+
* @returns {boolean} - true when nothing accepts the name.
|
|
1167
|
+
*/
|
|
1168
|
+
isUnknownToolbarName ({
|
|
1169
|
+
name,
|
|
1170
|
+
}) {
|
|
1171
|
+
return !this.generateKnownToolbarNames()
|
|
1172
|
+
.includes(name)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Fail on a `parcel.toolbar` name nothing can resolve, which would otherwise ship
|
|
1177
|
+
* as a quietly shorter toolbar. Production keeps dropping the name instead.
|
|
1178
|
+
*
|
|
1179
|
+
* @param {{
|
|
1180
|
+
* name: string
|
|
1181
|
+
* }} params - Parameters of this method.
|
|
1182
|
+
* @returns {void}
|
|
1183
|
+
* @throws {Error} When the name lies outside the toolbar vocabulary.
|
|
1184
|
+
*/
|
|
1185
|
+
throwOnUnknownToolbarName ({
|
|
1186
|
+
name,
|
|
1187
|
+
}) {
|
|
1188
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1189
|
+
return
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if (!this.isUnknownToolbarName({
|
|
1193
|
+
name,
|
|
1194
|
+
})) {
|
|
1195
|
+
return
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const acceptedNames = this.generateKnownToolbarNames()
|
|
1199
|
+
.join(', ')
|
|
1200
|
+
|
|
1201
|
+
throw new Error(`[FuroEditor] \`parcel.toolbar\` names "${name}", which is not a toolbar action. Accepted names: ${acceptedNames}.`)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* List the default action names no toolbar group named.
|
|
1206
|
+
*
|
|
1207
|
+
* @returns {Array<string>} - Hidden action names.
|
|
1208
|
+
*/
|
|
1209
|
+
generateOverflowEntryNames () {
|
|
1210
|
+
const namedNames = this.generateToolbarGroups()
|
|
1211
|
+
.flat()
|
|
1212
|
+
|
|
1213
|
+
return this.generateDefaultToolbarGroups()
|
|
1214
|
+
.flat()
|
|
1215
|
+
.filter(name => !namedNames.includes(name))
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Build the render descriptors the overflow menu holds.
|
|
1220
|
+
*
|
|
1221
|
+
* @returns {Array<FuroEditorToolbarEntry>} - Render descriptors.
|
|
1222
|
+
*/
|
|
1223
|
+
generateOverflowEntries () {
|
|
1224
|
+
const dropdownHash = this.generateDropdownDescriptorHash()
|
|
1225
|
+
const controlHash = this.generateControlHash()
|
|
1226
|
+
|
|
1227
|
+
return this.generateOverflowEntryNames()
|
|
1228
|
+
.map(name => this.resolveDropdownOrControlEntry({
|
|
1229
|
+
name,
|
|
1230
|
+
dropdownHash,
|
|
1231
|
+
controlHash,
|
|
1232
|
+
}))
|
|
1233
|
+
.filter(entry => entry !== null)
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Build the overflow menu options, one per hidden action.
|
|
1238
|
+
*
|
|
1239
|
+
* @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
|
|
1240
|
+
*/
|
|
1241
|
+
generateOverflowOptions () {
|
|
1242
|
+
return this.generateOverflowEntries()
|
|
1243
|
+
.map(entry => this.buildOverflowOption({
|
|
1244
|
+
entry,
|
|
1245
|
+
}))
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Build one overflow menu option, nesting a hidden dropdown as a submenu.
|
|
1250
|
+
*
|
|
1251
|
+
* @param {{
|
|
1252
|
+
* entry: FuroEditorToolbarEntry
|
|
1253
|
+
* }} params - Parameters of this method.
|
|
1254
|
+
* @returns {import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption} - Option.
|
|
1255
|
+
*/
|
|
1256
|
+
buildOverflowOption ({
|
|
1257
|
+
entry,
|
|
1258
|
+
}) {
|
|
1259
|
+
if (entry.type === 'dropdown') {
|
|
1260
|
+
return {
|
|
1261
|
+
label: entry.label,
|
|
1262
|
+
value: entry.key,
|
|
1263
|
+
type: 'submenu',
|
|
1264
|
+
options: entry.options,
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if (entry.control.kind !== 'toggle') {
|
|
1269
|
+
return {
|
|
1270
|
+
label: entry.control.tooltip,
|
|
1271
|
+
value: entry.control.name,
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
return {
|
|
1276
|
+
label: entry.control.tooltip,
|
|
1277
|
+
value: entry.control.name,
|
|
1278
|
+
type: 'checkbox',
|
|
1279
|
+
checked: this.isControlActive({
|
|
1280
|
+
control: entry.control,
|
|
1281
|
+
}),
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Extract the icon an overflow row draws, mirroring the on-bar active swap.
|
|
1287
|
+
*
|
|
1288
|
+
* @param {{
|
|
1289
|
+
* option: import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption
|
|
1290
|
+
* }} params - Parameters of this method.
|
|
1291
|
+
* @returns {string | null} - Icon name, or null when the row draws none.
|
|
1292
|
+
*/
|
|
1293
|
+
extractOverflowIconName ({
|
|
1294
|
+
option,
|
|
1295
|
+
}) {
|
|
1296
|
+
const name = option.value
|
|
1297
|
+
?? ''
|
|
1298
|
+
const dropdownEntry = this.generateDropdownDescriptorHash()[name]
|
|
1299
|
+
|
|
1300
|
+
if (dropdownEntry) {
|
|
1301
|
+
return dropdownEntry.iconName
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
const control = this.generateControlHash()[name]
|
|
1305
|
+
|
|
1306
|
+
if (!control) {
|
|
1307
|
+
return null
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
return this.isControlActive({
|
|
1311
|
+
control,
|
|
1312
|
+
})
|
|
1313
|
+
? control.activeIconName
|
|
1314
|
+
: control.iconName
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Whether an overflow row draws a colour swatch beside its label.
|
|
1319
|
+
*
|
|
1320
|
+
* @param {{
|
|
1321
|
+
* option: import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption
|
|
1322
|
+
* }} params - Parameters of this method.
|
|
1323
|
+
* @returns {boolean} - true when the row is a colour option.
|
|
1324
|
+
*/
|
|
1325
|
+
hasOverflowSwatch ({
|
|
1326
|
+
option,
|
|
1327
|
+
}) {
|
|
1328
|
+
return (option.value ?? '')
|
|
1329
|
+
.startsWith(COLOR_OPTION_PREFIX)
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* React to an overflow menu selection, routing to the handler the value belongs to.
|
|
1334
|
+
*
|
|
1335
|
+
* @param {{
|
|
1336
|
+
* value?: string
|
|
1337
|
+
* }} params - Parameters of this method.
|
|
1338
|
+
* @returns {void}
|
|
1339
|
+
*/
|
|
1340
|
+
onOverflowSelect ({
|
|
1341
|
+
value = '',
|
|
1342
|
+
}) {
|
|
1343
|
+
const control = this.generateControlHash()[value]
|
|
1344
|
+
|
|
1345
|
+
if (!control) {
|
|
1346
|
+
this.onDropdownSelect({
|
|
1347
|
+
value,
|
|
1348
|
+
})
|
|
1349
|
+
|
|
1350
|
+
return
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
this.onControlClick({
|
|
1354
|
+
control,
|
|
1355
|
+
})
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Build the flat toolbar layout (dropdowns, controls, separators between groups).
|
|
1360
|
+
*
|
|
1361
|
+
* @returns {Array<FuroEditorToolbarEntry>} - Ordered render descriptors.
|
|
1362
|
+
*/
|
|
1363
|
+
generateToolbarLayout () {
|
|
1364
|
+
const dropdownHash = this.generateDropdownDescriptorHash()
|
|
1365
|
+
const controlHash = this.generateControlHash()
|
|
1366
|
+
|
|
1367
|
+
const resolvedGroups = this.generateToolbarGroups()
|
|
1368
|
+
.map(group =>
|
|
1369
|
+
group
|
|
1370
|
+
.map(name => this.resolveToolbarEntry({
|
|
1371
|
+
name,
|
|
1372
|
+
dropdownHash,
|
|
1373
|
+
controlHash,
|
|
1374
|
+
}))
|
|
1375
|
+
.filter(entry => entry !== null)
|
|
1376
|
+
)
|
|
1377
|
+
.filter(group => group.length > 0)
|
|
1378
|
+
|
|
1379
|
+
return resolvedGroups.flatMap((group, index) => {
|
|
1380
|
+
const separator = index === 0
|
|
1381
|
+
? []
|
|
1382
|
+
: [
|
|
1383
|
+
/** @type {FuroEditorToolbarEntry} */ ({
|
|
1384
|
+
type: 'separator',
|
|
1385
|
+
key: `separator-${index}`,
|
|
1386
|
+
}),
|
|
1387
|
+
]
|
|
1388
|
+
|
|
1389
|
+
return [
|
|
1390
|
+
...separator,
|
|
1391
|
+
...group,
|
|
1392
|
+
]
|
|
1393
|
+
})
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* Build the heading dropdown options.
|
|
1398
|
+
*
|
|
1399
|
+
* @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
|
|
1400
|
+
*/
|
|
1401
|
+
generateHeadingOptions () {
|
|
1402
|
+
const labels = this.extractLabels()
|
|
1403
|
+
|
|
1404
|
+
return [
|
|
1405
|
+
{
|
|
1406
|
+
label: labels.normalText,
|
|
1407
|
+
value: 'paragraph',
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
label: labels.heading1,
|
|
1411
|
+
value: 'heading:1',
|
|
1412
|
+
},
|
|
1413
|
+
{
|
|
1414
|
+
label: labels.heading2,
|
|
1415
|
+
value: 'heading:2',
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
label: labels.heading3,
|
|
1419
|
+
value: 'heading:3',
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
label: labels.heading4,
|
|
1423
|
+
value: 'heading:4',
|
|
1424
|
+
},
|
|
1425
|
+
]
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Build the text-color dropdown options.
|
|
1430
|
+
*
|
|
1431
|
+
* @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
|
|
1432
|
+
*/
|
|
1433
|
+
generateColorOptions () {
|
|
1434
|
+
const labels = this.extractLabels()
|
|
1435
|
+
|
|
1436
|
+
return [
|
|
1437
|
+
{
|
|
1438
|
+
label: labels.colorDefault,
|
|
1439
|
+
value: 'color:',
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
label: labels.colorRed,
|
|
1443
|
+
value: `color:${COLOR_VALUES.red}`,
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
label: labels.colorBlue,
|
|
1447
|
+
value: `color:${COLOR_VALUES.blue}`,
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
label: labels.colorGreen,
|
|
1451
|
+
value: `color:${COLOR_VALUES.green}`,
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
label: labels.colorOrange,
|
|
1455
|
+
value: `color:${COLOR_VALUES.orange}`,
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
label: labels.colorPurple,
|
|
1459
|
+
value: `color:${COLOR_VALUES.purple}`,
|
|
1460
|
+
},
|
|
1461
|
+
]
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* Resolve the swatch color for a color dropdown option value.
|
|
1466
|
+
*
|
|
1467
|
+
* @param {{
|
|
1468
|
+
* value?: string
|
|
1469
|
+
* }} params - Parameters of this method.
|
|
1470
|
+
* @returns {string} - CSS color for the swatch dot.
|
|
1471
|
+
*/
|
|
1472
|
+
extractColorSwatch ({
|
|
1473
|
+
value,
|
|
1474
|
+
}) {
|
|
1475
|
+
const rawValue = value
|
|
1476
|
+
?? ''
|
|
1477
|
+
|
|
1478
|
+
const swatchColor = rawValue.replace(/^color:/u, '')
|
|
1479
|
+
|
|
1480
|
+
return swatchColor === ''
|
|
1481
|
+
? 'var(--color-foreground)'
|
|
1482
|
+
: swatchColor
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Build the text-align dropdown options.
|
|
1487
|
+
*
|
|
1488
|
+
* @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
|
|
1489
|
+
*/
|
|
1490
|
+
generateAlignOptions () {
|
|
1491
|
+
const labels = this.extractLabels()
|
|
1492
|
+
|
|
1493
|
+
return [
|
|
1494
|
+
{
|
|
1495
|
+
label: labels.alignLeft,
|
|
1496
|
+
value: 'align:left',
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
label: labels.alignCenter,
|
|
1500
|
+
value: 'align:center',
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
label: labels.alignRight,
|
|
1504
|
+
value: 'align:right',
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
label: labels.alignJustify,
|
|
1508
|
+
value: 'align:justify',
|
|
1509
|
+
},
|
|
1510
|
+
]
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* React to a formatting control click.
|
|
1515
|
+
*
|
|
1516
|
+
* @param {{
|
|
1517
|
+
* control: FuroEditorControl
|
|
1518
|
+
* }} params - Parameters of this method.
|
|
1519
|
+
* @returns {void}
|
|
1520
|
+
*/
|
|
1521
|
+
onControlClick ({
|
|
1522
|
+
control,
|
|
1523
|
+
}) {
|
|
1524
|
+
const override = this.parcel?.actionOverrides?.[control.name]
|
|
1525
|
+
|
|
1526
|
+
if (override) {
|
|
1527
|
+
override({
|
|
1528
|
+
control,
|
|
1529
|
+
command: this.commandContext,
|
|
1530
|
+
editor: this.editor,
|
|
1531
|
+
})
|
|
1532
|
+
|
|
1533
|
+
return
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const actionHash = {
|
|
1537
|
+
toggle: () => this.commandContext.toggleNodeOrMark({
|
|
1538
|
+
command: control.command,
|
|
1539
|
+
}),
|
|
1540
|
+
link: () => this.openLinkDialog(),
|
|
1541
|
+
image: () => this.openImagePicker(),
|
|
1542
|
+
mention: () => this.commandContext.insertText({
|
|
1543
|
+
text: '@',
|
|
1544
|
+
}),
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
actionHash[control.kind]?.()
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* Open a native file picker for images and insert the chosen files inline.
|
|
1552
|
+
*
|
|
1553
|
+
* @returns {void}
|
|
1554
|
+
*/
|
|
1555
|
+
openImagePicker () {
|
|
1556
|
+
if (!this.hasImageFeature()) {
|
|
1557
|
+
return
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const input = this.createImageFileInput()
|
|
1561
|
+
|
|
1562
|
+
input.addEventListener('change', () => {
|
|
1563
|
+
this.uploadAndInsertImages({
|
|
1564
|
+
files: input.files,
|
|
1565
|
+
position: null,
|
|
1566
|
+
})
|
|
1567
|
+
})
|
|
1568
|
+
|
|
1569
|
+
input.click()
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* Create a hidden multi-image file input element.
|
|
1574
|
+
*
|
|
1575
|
+
* @returns {HTMLInputElement} - The file input.
|
|
1576
|
+
*/
|
|
1577
|
+
createImageFileInput () {
|
|
1578
|
+
const input = document.createElement('input')
|
|
1579
|
+
|
|
1580
|
+
input.type = 'file'
|
|
1581
|
+
input.accept = 'image/*'
|
|
1582
|
+
input.multiple = true
|
|
1583
|
+
|
|
1584
|
+
return input
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Extract image files from a FileList-like value.
|
|
1589
|
+
*
|
|
1590
|
+
* @param {{
|
|
1591
|
+
* fileSource: FileList | null
|
|
1592
|
+
* }} params - Parameters of this method.
|
|
1593
|
+
* @returns {Array<File>} - Image files only.
|
|
1594
|
+
*/
|
|
1595
|
+
extractImageFiles ({
|
|
1596
|
+
fileSource,
|
|
1597
|
+
}) {
|
|
1598
|
+
if (!fileSource) {
|
|
1599
|
+
return []
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
return Array.from(fileSource)
|
|
1603
|
+
.filter(file => file.type.startsWith('image/'))
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* Extract the image files the editor embeds itself, empty without an upload adapter.
|
|
1608
|
+
*
|
|
1609
|
+
* @param {{
|
|
1610
|
+
* fileSource: FileList | null
|
|
1611
|
+
* }} params - Parameters of this method.
|
|
1612
|
+
* @returns {Array<File>} - Image files the editor takes.
|
|
1613
|
+
*/
|
|
1614
|
+
extractInsertableImageFiles ({
|
|
1615
|
+
fileSource,
|
|
1616
|
+
}) {
|
|
1617
|
+
if (!this.hasImageFeature()) {
|
|
1618
|
+
return []
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
return this.extractImageFiles({ fileSource })
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
/**
|
|
1625
|
+
* Extract the files the editor leaves for the consumer to store.
|
|
1626
|
+
*
|
|
1627
|
+
* @param {{
|
|
1628
|
+
* fileSource: FileList | null
|
|
1629
|
+
* }} params - Parameters of this method.
|
|
1630
|
+
* @returns {Array<File>} - Files the editor does not insert.
|
|
1631
|
+
*/
|
|
1632
|
+
extractUnconsumedFiles ({
|
|
1633
|
+
fileSource,
|
|
1634
|
+
}) {
|
|
1635
|
+
if (!fileSource) {
|
|
1636
|
+
return []
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
const insertableFiles = this.extractInsertableImageFiles({ fileSource })
|
|
1640
|
+
|
|
1641
|
+
return Array.from(fileSource)
|
|
1642
|
+
.filter(file => !insertableFiles.includes(file))
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/**
|
|
1646
|
+
* Upload image files through the consumer adapter and insert them inline.
|
|
1647
|
+
*
|
|
1648
|
+
* The first file lands at `position` (or the cursor when null); the rest follow
|
|
1649
|
+
* it in order. Uploads run sequentially to preserve document order.
|
|
1650
|
+
*
|
|
1651
|
+
* @param {{
|
|
1652
|
+
* files: FileList | Array<File> | null
|
|
1653
|
+
* position?: number | null
|
|
1654
|
+
* }} params - Parameters of this method.
|
|
1655
|
+
* @returns {void}
|
|
1656
|
+
*/
|
|
1657
|
+
uploadAndInsertImages ({
|
|
1658
|
+
files,
|
|
1659
|
+
position = null,
|
|
1660
|
+
}) {
|
|
1661
|
+
const imageFiles = this.extractImageFiles({
|
|
1662
|
+
fileSource: /** @type {FileList | null} */ (files),
|
|
1663
|
+
})
|
|
1664
|
+
|
|
1665
|
+
imageFiles.reduce(
|
|
1666
|
+
async (previousPromise, file, index) => {
|
|
1667
|
+
await previousPromise
|
|
1668
|
+
|
|
1669
|
+
const insertPosition = index === 0
|
|
1670
|
+
? position
|
|
1671
|
+
: null
|
|
1672
|
+
|
|
1673
|
+
await this.uploadAndInsertImage({
|
|
1674
|
+
file,
|
|
1675
|
+
position: insertPosition,
|
|
1676
|
+
})
|
|
1677
|
+
},
|
|
1678
|
+
Promise.resolve()
|
|
1679
|
+
)
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
/**
|
|
1683
|
+
* Upload one image and insert it, reporting a refusal instead of ending the batch.
|
|
1684
|
+
*
|
|
1685
|
+
* @param {{
|
|
1686
|
+
* file: File
|
|
1687
|
+
* position: number | null
|
|
1688
|
+
* }} params - Parameters of this method.
|
|
1689
|
+
* @returns {Promise<void>} - Resolves once the file is inserted or reported.
|
|
1690
|
+
*/
|
|
1691
|
+
async uploadAndInsertImage ({
|
|
1692
|
+
file,
|
|
1693
|
+
position,
|
|
1694
|
+
}) {
|
|
1695
|
+
const uploadImage = this.parcel?.image?.uploadImage
|
|
1696
|
+
|
|
1697
|
+
if (!uploadImage) {
|
|
1698
|
+
return
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
try {
|
|
1702
|
+
const uploaded = await uploadImage({ file })
|
|
1703
|
+
const altText = uploaded.alt
|
|
1704
|
+
?? ''
|
|
1705
|
+
|
|
1706
|
+
this.commandContext.insertImage({
|
|
1707
|
+
src: uploaded.src,
|
|
1708
|
+
alt: altText,
|
|
1709
|
+
position,
|
|
1710
|
+
})
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
this.emit(this.EMIT_EVENT_NAME.IMAGE_ERROR, {
|
|
1713
|
+
file,
|
|
1714
|
+
error,
|
|
1715
|
+
})
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/**
|
|
1720
|
+
* React to a dropdown option selection.
|
|
1721
|
+
*
|
|
1722
|
+
* @param {{
|
|
1723
|
+
* value?: string
|
|
1724
|
+
* }} params - Parameters of this method (value is the option's encoded action).
|
|
1725
|
+
* @returns {void}
|
|
1726
|
+
*/
|
|
1727
|
+
onDropdownSelect ({
|
|
1728
|
+
value = '',
|
|
1729
|
+
}) {
|
|
1730
|
+
const [
|
|
1731
|
+
kind,
|
|
1732
|
+
...rest
|
|
1733
|
+
] = value.split(':')
|
|
1734
|
+
const argument = rest.join(':')
|
|
1735
|
+
|
|
1736
|
+
const actionHash = {
|
|
1737
|
+
paragraph: () => this.commandContext.executeDropdownCommand({
|
|
1738
|
+
command: 'setParagraph',
|
|
1739
|
+
}),
|
|
1740
|
+
heading: () => this.commandContext.executeDropdownCommand({
|
|
1741
|
+
command: 'toggleHeading',
|
|
1742
|
+
level: Number(argument),
|
|
1743
|
+
}),
|
|
1744
|
+
color: () => this.commandContext.executeDropdownCommand({
|
|
1745
|
+
command: 'setColor',
|
|
1746
|
+
value: argument,
|
|
1747
|
+
}),
|
|
1748
|
+
align: () => this.commandContext.executeDropdownCommand({
|
|
1749
|
+
command: 'setTextAlign',
|
|
1750
|
+
value: argument,
|
|
1751
|
+
}),
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
actionHash[/** @type {keyof typeof actionHash} */ (kind)]?.()
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* Whether a formatting control is currently active.
|
|
1759
|
+
*
|
|
1760
|
+
* @param {{
|
|
1761
|
+
* control: FuroEditorControl
|
|
1762
|
+
* }} params - Parameters of this method.
|
|
1763
|
+
* @returns {boolean} - true when active.
|
|
1764
|
+
*/
|
|
1765
|
+
isControlActive ({
|
|
1766
|
+
control,
|
|
1767
|
+
}) {
|
|
1768
|
+
return this.commandContext.isActive({
|
|
1769
|
+
name: control.name,
|
|
1770
|
+
})
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* Extract the button variant for a control based on its active state.
|
|
1775
|
+
*
|
|
1776
|
+
* @param {{
|
|
1777
|
+
* control: FuroEditorControl
|
|
1778
|
+
* }} params - Parameters of this method.
|
|
1779
|
+
* @returns {'secondary' | 'ghost'} - Variant.
|
|
1780
|
+
*/
|
|
1781
|
+
extractControlVariant ({
|
|
1782
|
+
control,
|
|
1783
|
+
}) {
|
|
1784
|
+
return this.isControlActive({
|
|
1785
|
+
control,
|
|
1786
|
+
})
|
|
1787
|
+
? 'secondary'
|
|
1788
|
+
: 'ghost'
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
/**
|
|
1792
|
+
* Extract the trigger variant for a dropdown group based on its active state.
|
|
1793
|
+
*
|
|
1794
|
+
* @param {{
|
|
1795
|
+
* kind: 'heading' | 'textStyle' | 'textAlign'
|
|
1796
|
+
* }} params - Parameters of this method.
|
|
1797
|
+
* @returns {'secondary' | 'ghost'} - Variant.
|
|
1798
|
+
*/
|
|
1799
|
+
extractDropdownVariant ({
|
|
1800
|
+
kind,
|
|
1801
|
+
}) {
|
|
1802
|
+
return this.isDropdownActive({
|
|
1803
|
+
kind,
|
|
1804
|
+
})
|
|
1805
|
+
? 'secondary'
|
|
1806
|
+
: 'ghost'
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/**
|
|
1810
|
+
* Whether a dropdown group (heading / color / align) is active.
|
|
1811
|
+
*
|
|
1812
|
+
* @param {{
|
|
1813
|
+
* kind: 'heading' | 'textStyle' | 'textAlign'
|
|
1814
|
+
* }} params - Parameters of this method.
|
|
1815
|
+
* @returns {boolean} - true when active.
|
|
1816
|
+
*/
|
|
1817
|
+
isDropdownActive ({
|
|
1818
|
+
kind,
|
|
1819
|
+
}) {
|
|
1820
|
+
const activeHash = {
|
|
1821
|
+
heading: () => this.commandContext.isActive({
|
|
1822
|
+
name: 'heading',
|
|
1823
|
+
}),
|
|
1824
|
+
textStyle: () => this.commandContext.isActive({
|
|
1825
|
+
name: 'textStyle',
|
|
1826
|
+
}),
|
|
1827
|
+
textAlign: () => this.commandContext.hasActiveTextAlign({
|
|
1828
|
+
alignments: [
|
|
1829
|
+
'left',
|
|
1830
|
+
'center',
|
|
1831
|
+
'right',
|
|
1832
|
+
'justify',
|
|
1833
|
+
],
|
|
1834
|
+
}),
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
return activeHash[kind]?.()
|
|
1838
|
+
?? false
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
/**
|
|
1842
|
+
* Reflect the dialog open state from a FuroDialog open change.
|
|
1843
|
+
*
|
|
1844
|
+
* @param {{
|
|
1845
|
+
* isOpen: boolean
|
|
1846
|
+
* }} params - Parameters of this method.
|
|
1847
|
+
* @returns {void}
|
|
1848
|
+
*/
|
|
1849
|
+
onLinkDialogOpenChange ({
|
|
1850
|
+
isOpen,
|
|
1851
|
+
}) {
|
|
1852
|
+
this.statusReactive.isLinkDialogOpen = isOpen
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/**
|
|
1856
|
+
* Open the link dialog, prefilling from the current selection.
|
|
1857
|
+
*
|
|
1858
|
+
* @returns {void}
|
|
1859
|
+
*/
|
|
1860
|
+
openLinkDialog () {
|
|
1861
|
+
const selectionLink = this.commandContext.extractSelectionLink()
|
|
1862
|
+
|
|
1863
|
+
this.addLinkReactive.url = selectionLink.url
|
|
1864
|
+
this.addLinkReactive.displayText = selectionLink.displayText
|
|
1865
|
+
this.linkRange = selectionLink.range
|
|
1866
|
+
|
|
1867
|
+
this.statusReactive.isLinkDialogOpen = true
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* Close the link dialog.
|
|
1872
|
+
*
|
|
1873
|
+
* @returns {void}
|
|
1874
|
+
*/
|
|
1875
|
+
closeLinkDialog () {
|
|
1876
|
+
this.statusReactive.isLinkDialogOpen = false
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* Apply the link from the dialog and close it.
|
|
1881
|
+
*
|
|
1882
|
+
* @returns {void}
|
|
1883
|
+
*/
|
|
1884
|
+
saveLink () {
|
|
1885
|
+
const { url } = this.addLinkReactive
|
|
1886
|
+
const { displayText } = this.addLinkReactive
|
|
1887
|
+
const range = this.linkRange
|
|
1888
|
+
|
|
1889
|
+
this.closeLinkDialog()
|
|
1890
|
+
|
|
1891
|
+
this.addLinkReactive.url = ''
|
|
1892
|
+
this.addLinkReactive.displayText = ''
|
|
1893
|
+
|
|
1894
|
+
// Apply after the modal closes so focus returns to the editor before the edit.
|
|
1895
|
+
nextTick(() => {
|
|
1896
|
+
this.commandContext.applyLink({
|
|
1897
|
+
url,
|
|
1898
|
+
displayText,
|
|
1899
|
+
range,
|
|
1900
|
+
})
|
|
1901
|
+
|
|
1902
|
+
this.linkRange = null
|
|
1903
|
+
})
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
/**
|
|
1907
|
+
* Update the dialog link URL.
|
|
1908
|
+
*
|
|
1909
|
+
* @param {{
|
|
1910
|
+
* url?: string | null
|
|
1911
|
+
* }} params - Parameters of this method.
|
|
1912
|
+
* @returns {void}
|
|
1913
|
+
*/
|
|
1914
|
+
updateLinkUrl ({
|
|
1915
|
+
url = '',
|
|
1916
|
+
}) {
|
|
1917
|
+
this.addLinkReactive.url = url ?? ''
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/**
|
|
1921
|
+
* Update the dialog link display text.
|
|
1922
|
+
*
|
|
1923
|
+
* @param {{
|
|
1924
|
+
* displayText?: string | null
|
|
1925
|
+
* }} params - Parameters of this method.
|
|
1926
|
+
* @returns {void}
|
|
1927
|
+
*/
|
|
1928
|
+
updateLinkDisplayText ({
|
|
1929
|
+
displayText = '',
|
|
1930
|
+
}) {
|
|
1931
|
+
this.addLinkReactive.displayText = displayText ?? ''
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
/**
|
|
1935
|
+
* Remove an attached file by index and emit the updated array.
|
|
1936
|
+
*
|
|
1937
|
+
* @param {{
|
|
1938
|
+
* index: number
|
|
1939
|
+
* }} params - Parameters of this method.
|
|
1940
|
+
* @returns {void}
|
|
1941
|
+
*/
|
|
1942
|
+
removeAttachedFile ({
|
|
1943
|
+
index,
|
|
1944
|
+
}) {
|
|
1945
|
+
const nextFiles = this.attachedFiles
|
|
1946
|
+
.filter((file, fileIndex) => fileIndex !== index)
|
|
1947
|
+
|
|
1948
|
+
this.emit(this.EMIT_EVENT_NAME.UPDATE_ATTACHED_FILES, nextFiles)
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/**
|
|
1952
|
+
* Whether the consumer provided an `attachments` slot.
|
|
1953
|
+
*
|
|
1954
|
+
* @returns {boolean} - true when the slot is present.
|
|
1955
|
+
*/
|
|
1956
|
+
hasAttachmentsSlot () {
|
|
1957
|
+
return Boolean(this.slots?.attachments)
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* @typedef {{
|
|
1963
|
+
* name: string
|
|
1964
|
+
* kind: 'toggle' | 'link' | 'image' | 'mention'
|
|
1965
|
+
* command: string
|
|
1966
|
+
* iconName: string
|
|
1967
|
+
* activeIconName: string
|
|
1968
|
+
* tooltip: string
|
|
1969
|
+
* }} FuroEditorControl
|
|
1970
|
+
*/
|
|
1971
|
+
|
|
1972
|
+
/**
|
|
1973
|
+
* @typedef {{
|
|
1974
|
+
* type: 'separator'
|
|
1975
|
+
* key: string
|
|
1976
|
+
* } | {
|
|
1977
|
+
* type: 'dropdown'
|
|
1978
|
+
* key: string
|
|
1979
|
+
* label: string
|
|
1980
|
+
* iconName: string
|
|
1981
|
+
* dropdownKind: 'heading' | 'textStyle' | 'textAlign'
|
|
1982
|
+
* hasSwatch?: boolean
|
|
1983
|
+
* options: Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>
|
|
1984
|
+
* } | {
|
|
1985
|
+
* type: 'control'
|
|
1986
|
+
* key: string
|
|
1987
|
+
* control: FuroEditorControl
|
|
1988
|
+
* } | {
|
|
1989
|
+
* type: 'overflow'
|
|
1990
|
+
* key: string
|
|
1991
|
+
* }} FuroEditorToolbarEntry
|
|
1992
|
+
*/
|
|
1993
|
+
|
|
1994
|
+
/**
|
|
1995
|
+
* @typedef {{
|
|
1996
|
+
* isLinkDialogOpen: boolean
|
|
1997
|
+
* }} FuroEditorStatusReactive
|
|
1998
|
+
*/
|
|
1999
|
+
|
|
2000
|
+
/**
|
|
2001
|
+
* @typedef {{
|
|
2002
|
+
* url: string
|
|
2003
|
+
* displayText: string
|
|
2004
|
+
* }} FuroEditorAddLinkReactive
|
|
2005
|
+
*/
|
|
2006
|
+
|
|
2007
|
+
/**
|
|
2008
|
+
* @typedef {import('../../../contexts/BaseFuroContext.js').BaseFuroContextParams<{
|
|
2009
|
+
* parcel?: FuroEditorParcel | null
|
|
2010
|
+
* }> & {
|
|
2011
|
+
* editorRef: ReturnType<typeof import('@tiptap/vue-3').useEditor>
|
|
2012
|
+
* reactiveHash: {
|
|
2013
|
+
* statusReactive: import('vue').Reactive<FuroEditorStatusReactive>
|
|
2014
|
+
* addLinkReactive: import('vue').Reactive<FuroEditorAddLinkReactive>
|
|
2015
|
+
* }
|
|
2016
|
+
* }} FuroEditorContextParams
|
|
2017
|
+
*/
|
|
2018
|
+
|
|
2019
|
+
/**
|
|
2020
|
+
* @typedef {Omit<FuroEditorContextParams, 'reactiveHash'> & {
|
|
2021
|
+
* reactiveHash?: FuroEditorContextParams['reactiveHash']
|
|
2022
|
+
* }} FuroEditorContextFactoryParams
|
|
2023
|
+
*/
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* @typedef {{
|
|
2027
|
+
* value?: string
|
|
2028
|
+
* placeholder?: string
|
|
2029
|
+
* readOnly?: boolean
|
|
2030
|
+
* appearance?: 'editor' | 'plain'
|
|
2031
|
+
* autofocus?: boolean | 'start' | 'end' | 'all' | number
|
|
2032
|
+
* contentAttributes?: Record<string, string>
|
|
2033
|
+
* toolbar?: Array<Array<string>>
|
|
2034
|
+
* attachedFiles?: Array<*>
|
|
2035
|
+
* labels?: Partial<typeof DEFAULT_LABELS>
|
|
2036
|
+
* mention?: {
|
|
2037
|
+
* searchMentions: (params: { query: string }) => Promise<Array<{ id: string | number, label: string }>>
|
|
2038
|
+
* }
|
|
2039
|
+
* image?: {
|
|
2040
|
+
* uploadImage: (params: { file: File }) => Promise<{ src: string, alt?: string }>
|
|
2041
|
+
* }
|
|
2042
|
+
* actionOverrides?: Record<string, (params: {
|
|
2043
|
+
* control: FuroEditorControl
|
|
2044
|
+
* command: FuroEditorCommandContext
|
|
2045
|
+
* editor: import('@tiptap/vue-3').Editor | null
|
|
2046
|
+
* }) => void>
|
|
2047
|
+
* }} FuroEditorParcel
|
|
2048
|
+
*/
|