From 15c4550ccbd49bacce6e73c57f5e0f2ff2048fe4 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Tue, 9 Aug 2022 14:07:11 +0300 Subject: [PATCH 1/2] feat: add 'insert link' tool button to content editor in story form Fixes #119 --- .../InsertLinkModal.stories.tsx | 17 +++ .../InsertLinkModal/InsertLinkModal.tsx | 107 ++++++++++++++++++ .../TextEditor/InsertLinkModal/index.tsx | 4 + .../TextEditor/ToolButton/LinkToolBtn.tsx | 68 +++++++++++ .../Inputs/TextEditor/ToolButton/ToolBtn.tsx | 5 + .../Inputs/TextEditor/ToolButton/helpers.ts | 8 +- .../Components/ContentEditor/Toolbar.tsx | 3 +- src/redux/features/modals.slice.ts | 2 + 8 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.stories.tsx create mode 100644 src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.tsx create mode 100644 src/Components/Inputs/TextEditor/InsertLinkModal/index.tsx create mode 100644 src/Components/Inputs/TextEditor/ToolButton/LinkToolBtn.tsx diff --git a/src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.stories.tsx b/src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.stories.tsx new file mode 100644 index 0000000..96c2fd1 --- /dev/null +++ b/src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import InsertLinkModal from './InsertLinkModal'; + +import { ModalsDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Shared/Inputs/Text Editor/Insert Link Modal', + component: InsertLinkModal, + + decorators: [ModalsDecorator] +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.tsx b/src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.tsx new file mode 100644 index 0000000..290094e --- /dev/null +++ b/src/Components/Inputs/TextEditor/InsertLinkModal/InsertLinkModal.tsx @@ -0,0 +1,107 @@ +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' +import * as yup from "yup"; +import { SubmitHandler, useForm } from "react-hook-form" +import { yupResolver } from "@hookform/resolvers/yup"; + +interface Props extends ModalCard { + callbackAction: PayloadAction<{ href: string, text: string }> +} + +const schema = yup.object({ + text: yup.string().trim().required().min(2, 'Link text should be at least 2 characters'), + href: yup.string().trim().required().url(), + +}).required(); + + +export interface IFormInputs { + text: string, + href: string, +} + + +export default function InsertLinkModal({ onClose, direction, callbackAction, ...props }: Props) { + + const { register, formState: { errors }, handleSubmit } = useForm({ + defaultValues: { + href: '', + text: '', + }, + resolver: yupResolver(schema), + mode: 'onBlur', + }); + + const dispatch = useAppDispatch(); + + const onSubmit: SubmitHandler = data => { + const action = Object.assign({}, callbackAction); + action.payload = { text: data.text, href: data.href } + dispatch(action) + onClose?.(); + }; + + return ( + + +

Insert Link

+
+
+
+

+ Link Text +

+
+ +
+ {errors.text &&

+ {errors.text.message} +

} +
+
+

+ Link URL +

+
+ +
+ {errors.href &&

+ {errors.href.message} +

} +
+
+
+ + +
+
+ +
+ ) +} diff --git a/src/Components/Inputs/TextEditor/InsertLinkModal/index.tsx b/src/Components/Inputs/TextEditor/InsertLinkModal/index.tsx new file mode 100644 index 0000000..73eee05 --- /dev/null +++ b/src/Components/Inputs/TextEditor/InsertLinkModal/index.tsx @@ -0,0 +1,4 @@ + +import { lazyModal } from 'src/utils/helperFunctions'; + +export const { LazyComponent: InsertLinkModal } = lazyModal(() => import('./InsertLinkModal')) \ No newline at end of file diff --git a/src/Components/Inputs/TextEditor/ToolButton/LinkToolBtn.tsx b/src/Components/Inputs/TextEditor/ToolButton/LinkToolBtn.tsx new file mode 100644 index 0000000..48b3e54 --- /dev/null +++ b/src/Components/Inputs/TextEditor/ToolButton/LinkToolBtn.tsx @@ -0,0 +1,68 @@ +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_LINK_ACTION = createAction<{ href: string, text: string }>('LINK_INSERTED_IN_EDITOR')({ href: '', text: '' }) + +export default function LinkToolButton({ classes }: Props) { + + const commands = useCommands(); + + const dispatch = useAppDispatch() + + const onInsertLink = useCallback(({ payload: { href, text } }: typeof INSERT_LINK_ACTION) => { + commands.insertMarkdown(`[${text}](${href})`) + }, [commands]) + + useReduxEffect(onInsertLink, INSERT_LINK_ACTION.type) + + + + const { tip, Icon } = cmdToBtn['link']; + const onClick = () => { + dispatch(openModal({ + Modal: "InsertLinkModal", + props: { + callbackAction: { + type: INSERT_LINK_ACTION.type, + payload: { + href: "", + text: "", + } + } + } + })) + } + + return ( + + ) + + + +} + diff --git a/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx b/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx index d494ca0..0846b08 100644 --- a/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx +++ b/src/Components/Inputs/TextEditor/ToolButton/ToolBtn.tsx @@ -3,6 +3,7 @@ import HeadingsToolButton from './HeadingsToolBtn'; import DefaultToolButton from './DefaultToolBtn'; import { Command, isCommand } from './helpers'; import VideoToolButton from './VideoToolBtn'; +import LinkToolButton from './LinkToolBtn'; interface Props { cmd: Command @@ -43,6 +44,10 @@ export default function ToolButton({ cmd, if (cmd === 'img') return + + if (cmd === 'link') + return + return diff --git a/src/Components/Inputs/TextEditor/ToolButton/helpers.ts b/src/Components/Inputs/TextEditor/ToolButton/helpers.ts index c7dad6d..30e3142 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, FaYoutube, FaQuoteLeft } from 'react-icons/fa' +import { FaListOl, FaListUl, FaUndo, FaRedo, FaImage, FaYoutube, FaQuoteLeft, FaLink } from 'react-icons/fa' import { BiCodeCurly } from 'react-icons/bi'; @@ -97,6 +97,12 @@ export const cmdToBtn = { tip: "Insert Image", Icon: FaImage, }, + link: { + cmd: 'insertLink', + activeCmd: 'link', + tip: "Insert Link", + Icon: FaLink, + }, youtube: { cmd: 'addYouTubeVideo', diff --git a/src/features/Posts/pages/CreatePostPage/Components/ContentEditor/Toolbar.tsx b/src/features/Posts/pages/CreatePostPage/Components/ContentEditor/Toolbar.tsx index c29e678..d0f2e53 100644 --- a/src/features/Posts/pages/CreatePostPage/Components/ContentEditor/Toolbar.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/ContentEditor/Toolbar.tsx @@ -17,9 +17,10 @@ export default function Toolbar() { {/* */} - + + diff --git a/src/redux/features/modals.slice.ts b/src/redux/features/modals.slice.ts index a789876..6e05b12 100644 --- a/src/redux/features/modals.slice.ts +++ b/src/redux/features/modals.slice.ts @@ -4,6 +4,7 @@ import { ProjectDetailsCard } from "src/features/Projects/pages/ProjectPage/Proj import VoteCard from "src/features/Projects/pages/ProjectPage/VoteCard/VoteCard"; import { InsertImageModal } from 'src/Components/Inputs/TextEditor/InsertImageModal' import { InsertVideoModal } from 'src/Components/Inputs/TextEditor/InsertVideoModal' +import { InsertLinkModal } from 'src/Components/Inputs/TextEditor/InsertLinkModal' 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 { ConfirmModal } from "src/Components/Modals/ConfirmModal"; @@ -38,6 +39,7 @@ export const ALL_MODALS = { // Text Editor Modals InsertImageModal, InsertVideoModal, + InsertLinkModal, } type ExcludeBaseModalProps = Omit From 28eadcc1ffbe9198c9d92e48240c77fea79af535 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Tue, 9 Aug 2022 14:25:17 +0300 Subject: [PATCH 2/2] fix: remove old userKeys when linking key --- api/functions/login/login.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/functions/login/login.js b/api/functions/login/login.js index 7dcdbf2..cf731e9 100644 --- a/api/functions/login/login.js +++ b/api/functions/login/login.js @@ -39,7 +39,12 @@ const loginHandler = async (req, res) => { return res.status(400).json({ status: 'ERROR', reason: "Can only link up to 3 wallets" }) if (existingKeys.includes(key)) - return res.status(400).json({ status: 'ERROR', reason: "Wallet already linked" }) + return res.status(400).json({ status: 'ERROR', reason: "Wallet already linked" }); + + // Remove old linking for this key if existing + await prisma.userKey.delete({ + where: { key } + }) await prisma.userKey.create({