'use client'; import React, { FC, useCallback, useEffect, useMemo, useRef, useState, ClipboardEvent, forwardRef, useImperativeHandle, Fragment, } from 'react'; import clsx from 'clsx'; import { useUser } from '@gitroom/frontend/components/layout/user.context'; import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; import EmojiPicker from 'emoji-picker-react'; import { Theme } from 'emoji-picker-react'; import { BoldText } from '@gitroom/frontend/components/new-launch/bold.text'; import { UText } from '@gitroom/frontend/components/new-launch/u.text'; import { SignatureBox } from '@gitroom/frontend/components/signature'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import { SelectedIntegrations, useLaunchStore, } from '@gitroom/frontend/components/new-launch/store'; import { useShallow } from 'zustand/react/shallow'; import { AddPostButton } from '@gitroom/frontend/components/new-launch/add.post.button'; import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component'; import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow'; import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core'; import { useDropzone } from 'react-dropzone'; import { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader'; import { Dashboard } from '@uppy/react'; import Link from '@tiptap/extension-link'; import { useEditor, EditorContent, Extension, mergeAttributes, Node, } from '@tiptap/react'; import Document from '@tiptap/extension-document'; import Bold from '@tiptap/extension-bold'; import Text from '@tiptap/extension-text'; import Paragraph from '@tiptap/extension-paragraph'; import Underline from '@tiptap/extension-underline'; import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; import { History } from '@tiptap/extension-history'; import { BulletList, ListItem } from '@tiptap/extension-list'; import { Bullets } from '@gitroom/frontend/components/new-launch/bullets.component'; import Heading from '@tiptap/extension-heading'; import { HeadingComponent } from '@gitroom/frontend/components/new-launch/heading.component'; import Mention from '@tiptap/extension-mention'; import { suggestion } from '@gitroom/frontend/components/new-launch/mention.component'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { AComponent } from '@gitroom/frontend/components/new-launch/a.component'; import { capitalize } from 'lodash'; const InterceptBoldShortcut = Extension.create({ name: 'preventBoldWithUnderline', addKeyboardShortcuts() { return { 'Mod-b': () => { // For example, toggle bold while removing underline this?.editor?.commands?.unsetUnderline(); return this?.editor?.commands?.toggleBold(); }, }; }, }); const InterceptUnderlineShortcut = Extension.create({ name: 'preventUnderlineWithUnderline', addKeyboardShortcuts() { return { 'Mod-u': () => { // For example, toggle bold while removing underline this?.editor?.commands?.unsetBold(); return this?.editor?.commands?.toggleUnderline(); }, }; }, }); export const EditorWrapper: FC<{ totalPosts: number; value: string; }> = (props) => { const { setGlobalValueText, setInternalValueText, addRemoveInternal, internal, global, current, addInternalValue, addGlobalValue, setInternalValueMedia, appendInternalValueMedia, appendGlobalValueMedia, setGlobalValueMedia, changeOrderGlobal, changeOrderInternal, isCreateSet, deleteGlobalValue, deleteInternalValue, setGlobalValue, setInternalValue, internalFromAll, totalChars, postComment, dummy, editor, loadedState, setLoadedState, selectedIntegration, chars, } = useLaunchStore( useShallow((state) => ({ internal: state.internal.find((p) => p.integration.id === state.current), internalFromAll: state.integrations.find((p) => p.id === state.current), global: state.global, current: state.current, addRemoveInternal: state.addRemoveInternal, dummy: state.dummy, setInternalValueText: state.setInternalValueText, setGlobalValueText: state.setGlobalValueText, addInternalValue: state.addInternalValue, addGlobalValue: state.addGlobalValue, setGlobalValueMedia: state.setGlobalValueMedia, setInternalValueMedia: state.setInternalValueMedia, changeOrderGlobal: state.changeOrderGlobal, changeOrderInternal: state.changeOrderInternal, isCreateSet: state.isCreateSet, deleteGlobalValue: state.deleteGlobalValue, deleteInternalValue: state.deleteInternalValue, setGlobalValue: state.setGlobalValue, setInternalValue: state.setInternalValue, totalChars: state.totalChars, appendInternalValueMedia: state.appendInternalValueMedia, appendGlobalValueMedia: state.appendGlobalValueMedia, postComment: state.postComment, editor: state.editor, loadedState: state.loaded, setLoadedState: state.setLoaded, selectedIntegration: state.selectedIntegrations, chars: state.chars, })) ); const existingData = useExistingData(); const [loaded, setLoaded] = useState(true); useEffect(() => { if (loaded && loadedState) { return; } setLoadedState(true); setLoaded(true); }, [loaded, loadedState]); const canEdit = useMemo(() => { return current === 'global' || !!internal; }, [current, internal]); const items = useMemo(() => { if (internal) { return internal.integrationValue; } return global; }, [internal, global]); const setValue = useCallback( (value: string[]) => { const newValue = value.map((p, index) => { return { id: makeId(10), ...(items?.[index]?.media ? { media: items[index].media } : { media: [] }), content: p, }; }); if (internal) { return setInternalValue(current, newValue); } return setGlobalValue(newValue); }, [internal, items] ); useCopilotReadable({ description: 'Current content of posts', value: items.map((p) => p.content), }); useCopilotAction({ name: 'setPosts', description: 'a thread of posts', parameters: [ { name: 'content', type: 'string[]', description: 'a thread of posts', }, ], handler: async ({ content }) => { setValue(content); }, }); const changeValue = useCallback( (index: number) => (value: string) => { if (internal) { return setInternalValueText(current, index, value); } return setGlobalValueText(index, value); }, [current, global, internal] ); const changeImages = useCallback( (index: number) => (value: any[]) => { if (internal) { return setInternalValueMedia(current, index, value); } return setGlobalValueMedia(index, value); }, [current, global, internal] ); const appendImages = useCallback( (index: number) => (value: any[]) => { if (internal) { return appendInternalValueMedia(current, index, value); } return appendGlobalValueMedia(index, value); }, [current, global, internal] ); const changeOrder = useCallback( (index: number) => (direction: 'up' | 'down') => { if (internal) { changeOrderInternal(current, index, direction); return setLoaded(false); } changeOrderGlobal(index, direction); setLoaded(false); }, [changeOrderInternal, changeOrderGlobal, current, global, internal] ); const goBackToGlobal = useCallback(async () => { if ( await deleteDialog( 'This action is irreversible. Are you sure you want to go back to global mode?', 'Yes, go back to global mode' ) ) { setLoaded(false); addRemoveInternal(current); } }, [addRemoveInternal, current]); const addValue = useCallback( (index: number) => () => { if (internal) { return addInternalValue(index, current, [ { content: '', id: makeId(10), media: [], }, ]); } return addGlobalValue(index, [ { content: '', id: makeId(10), media: [], }, ]); }, [current, global, internal] ); const deletePost = useCallback( (index: number) => async () => { if ( !(await deleteDialog( 'Are you sure you want to delete this post?', 'Yes, delete it!' )) ) { return; } if (internal) { deleteInternalValue(current, index); return setLoaded(false); } deleteGlobalValue(index); setLoaded(false); }, [current, global, internal] ); if (!loaded || !loadedState) { return null; } return (