diff --git a/src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal.stories.tsx b/src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal.stories.tsx new file mode 100644 index 0000000..39e8409 --- /dev/null +++ b/src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import InsertVideoModal from './InsertVideoModal'; + +import { ModalsDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Shared/Inputs/Text Editor/Insert Video Modal', + component: InsertVideoModal, + + decorators: [ModalsDecorator] +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal.tsx b/src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal.tsx new file mode 100644 index 0000000..7c211e7 --- /dev/null +++ b/src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal.tsx @@ -0,0 +1,70 @@ +import React, { FormEvent, useState } from 'react' +import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer' +import { motion } from 'framer-motion' +import { IoClose } from 'react-icons/io5' +import Button from 'src/Components/Button/Button' +import { useAppDispatch } from 'src/utils/hooks' +import { PayloadAction } from '@reduxjs/toolkit' + +interface Props extends ModalCard { + callbackAction: PayloadAction<{ src: string, alt?: string }> +} + +export default function InsertVideoModal({ onClose, direction, callbackAction, ...props }: Props) { + + const [idInput, setIdInput] = useState("") + const [altInput, setAltInput] = useState("") + const dispatch = useAppDispatch(); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault() + if (idInput.length > 10) { + // onInsert({ src: idInput, alt: altInput }) + const action = Object.assign({}, callbackAction); + action.payload = { src: idInput, alt: altInput } + dispatch(action) + onClose?.(); + } + } + + return ( + + +

Insert Youtube Video

+
+
+
+

+ Video Id +

+
+ setIdInput(e.target.value)} + placeholder='Zi7sRMcJT-o' + /> +
+
+
+
+ + +
+
+ +
+ ) +} diff --git a/src/Components/Inputs/TextEditor/TextEditor.stories.tsx b/src/Components/Inputs/TextEditor/TextEditor.stories.tsx index df88e68..bdae316 100644 --- a/src/Components/Inputs/TextEditor/TextEditor.stories.tsx +++ b/src/Components/Inputs/TextEditor/TextEditor.stories.tsx @@ -75,10 +75,7 @@ WithPreview.args = { #### heading4 -###### heading6 -
-
-
+###### heading6 some text with **bold**, _italic,_ underline, [www.link.com](//www.link.com) diff --git a/src/Components/Inputs/TextEditor/TextEditor.tsx b/src/Components/Inputs/TextEditor/TextEditor.tsx index 9dec89d..a3e6a1a 100644 --- a/src/Components/Inputs/TextEditor/TextEditor.tsx +++ b/src/Components/Inputs/TextEditor/TextEditor.tsx @@ -23,6 +23,7 @@ import { TableExtension, TrailingNodeExtension, UnderlineExtension, + IframeExtension, } from 'remirror/extensions'; import { ExtensionPriority, InvalidContentHandler } from 'remirror'; import { EditorComponent, Remirror, useHelpers, useRemirror } from '@remirror/react'; @@ -77,6 +78,7 @@ export default function TextEditor({ placeholder, initialContent }: Props) { // new TableExtension(), new MarkdownExtension({ copyAsMarkdown: false }), new NodeFormattingExtension(), + new IframeExtension(), /** * `HardBreakExtension` allows us to create a newline inside paragraphs. * e.g. in a list item diff --git a/src/Components/Inputs/TextEditor/ToolButton/ImageToolBtn.tsx b/src/Components/Inputs/TextEditor/ToolButton/ImageToolBtn.tsx index f294fc7..bb6b5ed 100644 --- a/src/Components/Inputs/TextEditor/ToolButton/ImageToolBtn.tsx +++ b/src/Components/Inputs/TextEditor/ToolButton/ImageToolBtn.tsx @@ -32,7 +32,7 @@ export default function ImageToolButton({ classes }: Props) { }) }, [commands]) - useReduxEffect(onInsertImage, 'INSERT_IMAGE_IN_EDITOR') + useReduxEffect(onInsertImage, INSERT_IMAGE_ACTION.type) @@ -42,7 +42,7 @@ export default function ImageToolButton({ classes }: Props) { Modal: "InsertImageModal", props: { callbackAction: { - type: 'INSERT_IMAGE_IN_EDITOR', + type: INSERT_IMAGE_ACTION.type, payload: { src: "", alt: "" diff --git a/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx b/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx index 67f8ccf..d494ca0 100644 --- a/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx +++ b/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx @@ -2,6 +2,7 @@ import ImageToolButton from './ImageToolBtn'; import HeadingsToolButton from './HeadingsToolBtn'; import DefaultToolButton from './DefaultToolBtn'; import { Command, isCommand } from './helpers'; +import VideoToolButton from './VideoToolBtn'; interface Props { cmd: Command @@ -36,6 +37,9 @@ export default function ToolButton({ cmd, return + if (cmd === 'youtube') + return + if (cmd === 'img') return diff --git a/src/Components/Inputs/TextEditor/ToolButton/VideoToolBtn.tsx b/src/Components/Inputs/TextEditor/ToolButton/VideoToolBtn.tsx new file mode 100644 index 0000000..96ba4e7 --- /dev/null +++ b/src/Components/Inputs/TextEditor/ToolButton/VideoToolBtn.tsx @@ -0,0 +1,71 @@ +import { useActive, useCommands } from '@remirror/react'; +import { useAppDispatch } from 'src/utils/hooks'; +import { openModal } from 'src/redux/features/modals.slice'; +import { useReduxEffect } from 'src/utils/hooks/useReduxEffect'; +import { useCallback } from 'react'; +import { createAction } from '@reduxjs/toolkit'; +import { cmdToBtn } from './helpers'; + +interface Props { + classes: { + button: string, + icon: string, + active: string, + enabled: string + disabled: string + } +} + +const INSERT_VIDEO_ACTION = createAction<{ src: string }>('VIDEO_INSERTED_IN_EDITOR')({ src: '' }) + +export default function VideoToolButton({ classes }: Props) { + + const commands = useCommands(); + const active = useActive(); + const dispatch = useAppDispatch() + + + const onInsertVideo = useCallback(({ payload: { src } }: typeof INSERT_VIDEO_ACTION) => { + commands.addYouTubeVideo({ + video: src, + + }) + }, [commands]) + + useReduxEffect(onInsertVideo, INSERT_VIDEO_ACTION.type) + + + const { activeCmd, cmd, tip, Icon } = cmdToBtn['youtube']; + const onClick = () => { + dispatch(openModal({ + Modal: "InsertVideoModal", + props: { + callbackAction: { + type: INSERT_VIDEO_ACTION.type, + payload: { + src: "", + } + } + } + })) + } + + return ( + + ) + + + +} + diff --git a/src/Components/Inputs/TextEditor/ToolButton/helpers.ts b/src/Components/Inputs/TextEditor/ToolButton/helpers.ts index 3abad2e..2c7cac2 100644 --- a/src/Components/Inputs/TextEditor/ToolButton/helpers.ts +++ b/src/Components/Inputs/TextEditor/ToolButton/helpers.ts @@ -1,5 +1,5 @@ import { FiBold, FiItalic, FiType, FiUnderline, FiAlignCenter, FiAlignLeft, FiAlignRight, FiCode } from 'react-icons/fi' -import { FaListOl, FaListUl, FaUndo, FaRedo, FaImage } from 'react-icons/fa' +import { FaListOl, FaListUl, FaUndo, FaRedo, FaImage, FaYoutube } from 'react-icons/fa' import { BiCodeCurly } from 'react-icons/bi'; @@ -92,6 +92,13 @@ export const cmdToBtn = { Icon: FaImage, }, + youtube: { + cmd: 'addYouTubeVideo', + activeCmd: 'iframe', + tip: "Insert Video", + Icon: FaYoutube, + }, + } as const diff --git a/src/Components/Inputs/TextEditor/Toolbar/Toolbar.tsx b/src/Components/Inputs/TextEditor/Toolbar/Toolbar.tsx index f41fdc5..265bbb5 100644 --- a/src/Components/Inputs/TextEditor/Toolbar/Toolbar.tsx +++ b/src/Components/Inputs/TextEditor/Toolbar/Toolbar.tsx @@ -19,6 +19,7 @@ export default function Toolbar() { + diff --git a/src/redux/features/modals.slice.ts b/src/redux/features/modals.slice.ts index a4657b7..194ed2d 100644 --- a/src/redux/features/modals.slice.ts +++ b/src/redux/features/modals.slice.ts @@ -3,6 +3,7 @@ import { Login_ScanningWalletCard, Login_ExternalWalletCard, Login_NativeWalletC import { ProjectDetailsCard } from "src/features/Projects/pages/ProjectPage/ProjectDetailsCard"; import VoteCard from "src/features/Projects/pages/ProjectPage/VoteCard/VoteCard"; import InsertImageModal from 'src/Components/Inputs/TextEditor/InsertImageModal/InsertImageModal' +import InsertVideoModal from 'src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal' import { Claim_FundWithdrawCard, Claim_CopySignatureCard, Claim_GenerateSignatureCard, Claim_SubmittedCard } from "src/features/Projects/pages/ProjectPage/ClaimProject"; import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer"; import { ComponentProps } from "react"; @@ -28,7 +29,10 @@ export const ALL_MODALS = { Claim_CopySignatureCard, Claim_SubmittedCard, Claim_FundWithdrawCard, - InsertImageModal + + // Text Editor Modals + InsertImageModal, + InsertVideoModal, } type ExcludeBaseModalProps = Omit diff --git a/src/styles/vendors.scss b/src/styles/vendors.scss index d995f04..af1fd04 100644 --- a/src/styles/vendors.scss +++ b/src/styles/vendors.scss @@ -1,36 +1,41 @@ - -// +// // This file is for overriding various libraries native styles // ----------------------------------------------------------- - // Re mirror // --------------- .remirror-editor-wrapper ul { - list-style: disc !important; - padding: revert; - margin: revert; + list-style: disc !important; + padding: revert; + margin: revert; } .remirror-editor-wrapper ol { - list-style: decimal !important; - padding: revert; - margin: revert; + list-style: decimal !important; + padding: revert; + margin: revert; +} + +.remirror-editor-wrapper iframe, +.remirror-theme iframe { + width: calc(min(100%, 550px)); + margin: 0 auto; + aspect-ratio: 16/9; } // React Modals // ---------------- .ReactModal__Overlay { - opacity: 0; - transition: opacity 900ms ease-in-out; + opacity: 0; + transition: opacity 900ms ease-in-out; } .ReactModal__Overlay--after-open { - opacity: 1; + opacity: 1; } .ReactModal__Overlay--before-close { - opacity: 0; - transition-timing-function: ease-in; - transition-duration: 400ms; + opacity: 0; + transition-timing-function: ease-in; + transition-duration: 400ms; }