import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useEditor, EditorContent } from '@tiptap/react'; import { useCMEditViewDataManager } from '@strapi/helper-plugin'; import { Editor } from '@tiptap/core'; import { type IInternalLink, createInternalLink, INTERNAL_LINK_TYPE } from '@webbio/strapi-plugin-internal-links/admin/src/components/factory/index'; import { Field, FieldError, FieldHint, FieldLabel } from '@strapi/design-system'; import CustomKit from '../CustomKit'; import Toolbar from '../Toolbar'; import Footer from '../Footer'; import MediaLibrary, { IFile } from '../MediaLibrary'; import MenuLink from '../MenuLink'; import InternalLink from '../InternalLink'; import { decodeAttributes, encodeAttributes } from '../InternalLink/utils/utils'; import * as S from './styles'; interface IInputProps { contentTypeUID: any; attribute: any; onChange: any; name: any; value: any; disabled: any; labelAction: any; intlLabel: any; required: any; description: any; error: any; } const Input = ({ attribute, onChange, name, value, disabled, labelAction, intlLabel, required, description, error, contentTypeUID }: IInputProps) => { const limit = attribute?.options?.maxLengthCharacters; const { formatMessage } = useIntl(); const { initialData } = useCMEditViewDataManager(); const [showMediaLibrary, setShowMediaLibrary] = useState(false); const [forceInsert, setForceInsert] = useState(false); const [linkModalOpen, setShowLinkModal] = useState(false); const [link, setLink] = useState(); const [hasUpdated, setHasUpdated] = useState(false); const editor = useEditor({ extensions: [ CustomKit.configure({ link: { protocols: ['mailto'], autolink: true, openOnClick: false, linkOnPaste: true }, taskItem: { nested: true }, characterCount: { limit }, table: { resizable: true } }) ], content: value, editable: !disabled, onUpdate: ({ editor }) => { setHasUpdated(true); onChange({ target: { name, value: editor.getHTML() } }); } }); const onSelectionChange = (): void => { if (!editor) return; const { state } = editor; const { from, to } = state.selection; const linkText = editor.isActive('button') ? editor.state.selection.$head.parent.textContent : state.doc.textBetween(from, to, ' '); const link = getLinkFromEditor(editor, contentTypeUID, initialData?.id, name, linkText); setLink(link); if (editor.isActive('link') && !editor.isActive('button') && from === to) { const { pos, textOffset, nodeAfter } = editor.state.selection.$anchor; if (textOffset === 0) return; editor.commands.setTextSelection({ from: pos - textOffset, to: pos + (nodeAfter?.nodeSize || 0) }); } }; // Empty/fill url value when element selection changes useEffect(onSelectionChange, [editor?.state.selection]); useEffect(() => { if (editor && editor.getHTML() !== value) { const keys = name.split('.'); const initialContent = keys.reduce( (obj: any, key: any) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), initialData ); if (editor.getHTML() === initialContent) { editor.commands.setContent(value); } } // Update the editor with the new content as long as the user hasnt typed anything yet. // This is neccesary because the content can be set multiple times in quick succession because of the draft plugin. if (editor && !hasUpdated) { editor.commands.setContent(value); } }, [value]); if (!editor) return <>; const mapAssetToImage = (asset: any) => ({ src: asset.url, alt: asset.alt, ...(asset.width && { width: asset.width }), ...(asset.height && { height: asset.height }), ...(asset.url?.includes('lazy') || (asset.caption === 'lazy' && { loading: 'lazy' })) }); const onMediaLibraryChange = (assets: Array) => { if (!forceInsert && editor.isActive('image')) { assets.map((asset) => { if (asset.mime.includes('image')) { editor.chain().focus().setImage(mapAssetToImage(asset)).run(); } }); } else { assets.map((asset) => { if (asset.mime.includes('image')) { editor.commands.setImage(mapAssetToImage(asset)); } }); } setForceInsert(false); setShowMediaLibrary((previous) => !previous); }; const onSubmitUrl = (link: IInternalLink): void => { if (!link?.url) return; setLink(link); editor.commands.setLink({ href: link.url, target: '_blank', //@ts-ignore 'data-internal-link': encodeAttributes(link) }); if (editor.isActive('button')) { const { pos, parentOffset, nodeAfter } = editor.state.selection.$anchor; editor .chain() .focus() .insertContentAt( { from: pos - parentOffset, to: pos + (nodeAfter?.nodeSize || 0) }, decodeURIComponent(link.text) ) .run(); } else { const selection = editor.view.state.selection; editor .chain() .focus() .insertContentAt( { from: selection.from, to: selection.to }, decodeURIComponent(link.text) ) .run(); } markerToNodeEnd(); }; const markerToNodeEnd = (): void => { const { pos, nodeAfter } = editor.state.selection.$anchor; editor.commands.setTextSelection(pos + (nodeAfter?.nodeSize || 0) + 1); editor.commands.focus(); }; return ( {formatMessage(intlLabel)} setShowMediaLibrary((previous) => !previous)} />