diff --git a/stories/link/Link.stories.js b/stories/link/Link.stories.js index bd253f7146723fe1c0405e44080ae155cfbd04b0..baf2e0d621d926ae653632fd152a43ffd4e67f90 100644 --- a/stories/link/Link.stories.js +++ b/stories/link/Link.stories.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { random } from 'faker'; -import LinkTooltip from '../../wax-prosemirror-components/src/ui/Link/LinkTooltip'; +import LinkTooltip from '../../wax-prosemirror-components/src/ui/link/LinkTooltip'; import { Demo, Note } from '../_helpers'; diff --git a/wax-prosemirror-components/index.js b/wax-prosemirror-components/index.js index 5b4c55391e5c6ea670272dadbcf8b315472d239c..26f3d8d5a60a50f188ea5da378ed070eafbaca55 100644 --- a/wax-prosemirror-components/index.js +++ b/wax-prosemirror-components/index.js @@ -16,6 +16,4 @@ export { default as RightArea } from './src/components/rightArea/RightArea'; export { default as TrackChangeEnable } from './src/components/trackChanges/TrackChangeEnable'; -export { default as InsertTableTool } from './src/ui/tables/InsertTableTool'; - export { default as CreateTable } from './src/components/tables/CreateTable'; diff --git a/wax-prosemirror-components/src/components/comments/Comment.js b/wax-prosemirror-components/src/components/comments/Comment.js deleted file mode 100644 index 08cc0155ccf49633d428075c02fcb54711093aa0..0000000000000000000000000000000000000000 --- a/wax-prosemirror-components/src/components/comments/Comment.js +++ /dev/null @@ -1,162 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { useState, useRef, useEffect } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import { last } from 'lodash'; -import styled from 'styled-components'; -import { DocumentHelpers } from 'wax-prosemirror-utilities'; - -const SinlgeCommentRow = styled.div` - padding: 4px; - border-bottom: 1px solid #ffab20; - cursor: pointer; -`; - -export default ({ comment, activeView, user, active }) => { - const commentInput = useRef(null); - const [commentAnnotation, setCommentAnnotation] = useState(comment); - const [commentInputValue, setcommentInputValue] = useState(''); - const { state, dispatch } = activeView; - const allCommentsWithSameId = DocumentHelpers.findAllMarksWithSameId( - state, - comment, - ); - const { - attrs: { conversation }, - } = comment; - const commentMark = state.schema.marks.comment; - - useEffect(() => { - setTimeout(() => { - if (commentInput.current !== null && conversation.length === 0) - commentInput.current.focus(); - }, 500); - }, [active]); - - const handleKeyDown = event => { - if (event.key === 'Enter' || event.which === 13) { - saveComment(event); - } - }; - - const saveComment = event => { - event.stopPropagation(); - - const { - current: { value }, - } = commentInput; - const { tr } = state; - - const obj = { - content: value, - displayName: user.username, - timestamp: Math.floor(Date.now() / 300000), - }; - - commentAnnotation.attrs.conversation.push(obj); - - allCommentsWithSameId.forEach(singleComment => { - dispatch( - tr.addMark( - singleComment.pos, - singleComment.pos + singleComment.nodeSize, - commentMark.create({ - ...((commentAnnotation && commentAnnotation.attrs) || {}), - conversation: commentAnnotation.attrs.conversation, - }), - ), - ); - }); - - setcommentInputValue(''); - }; - - const updateCommentInputValue = () => { - const { - current: { value }, - } = commentInput; - setcommentInputValue(value); - }; - - const onBlur = () => { - const { - current: { value }, - } = commentInput; - if (value !== '') { - // saveComment(); - } - - if (conversation.length === 0 && value === '') { - resolveComment(); - } - }; - - const resolveComment = event => { - if (event) event.stopPropagation(); - let maxPos = comment.pos; - let minPos = comment.pos; - - allCommentsWithSameId.forEach(singleComment => { - const markPosition = DocumentHelpers.findMarkPosition( - state, - singleComment.pos, - 'comment', - ); - if (markPosition.from < minPos) minPos = markPosition.from; - if (markPosition.to > maxPos) maxPos = markPosition.to; - }); - - if (allCommentsWithSameId.length > 1) - maxPos += last(allCommentsWithSameId).node.nodeSize; - dispatch(state.tr.removeMark(minPos, maxPos, commentMark)); - activeView.focus(); - }; - - const commentInputReply = () => { - return ( - <> - <input - type="text" - ref={commentInput} - placeholder="add a new comment" - onChange={updateCommentInputValue} - onKeyPress={handleKeyDown} - onBlur={onBlur} - onClick={event => { - event.stopPropagation(); - }} - value={commentInputValue} - disabled={!active} - /> - <button - disabled={!active} - type="button" - onClick={event => saveComment(event)} - > - Post - </button> - <button - disabled={!active} - type="button" - onClick={event => resolveComment(event)} - > - Resolve - </button> - </> - ); - }; - - return conversation.length === 0 ? ( - <>{commentInputReply()}</> - ) : ( - <> - {conversation.map((singleComment, index) => { - return ( - <SinlgeCommentRow key={uuidv4()}> - {`${singleComment.displayName} : ${singleComment.content}`} - </SinlgeCommentRow> - ); - })} - {commentInputReply()} - </> - ); -}; diff --git a/wax-prosemirror-components/src/components/comments/CommentBox.js b/wax-prosemirror-components/src/components/comments/CommentBox.js deleted file mode 100644 index 94ab170c9da58450365e25b770cf1267456bce63..0000000000000000000000000000000000000000 --- a/wax-prosemirror-components/src/components/comments/CommentBox.js +++ /dev/null @@ -1,117 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { useState, useEffect, useContext, memo } from 'react'; -import { TextSelection } from 'prosemirror-state'; -import { last, maxBy } from 'lodash'; - -import { Transition } from 'react-transition-group'; -import styled from 'styled-components'; -import { DocumentHelpers } from 'wax-prosemirror-utilities'; -import { WaxContext } from 'wax-prosemirror-core'; -import Comment from './Comment'; - -const CommentBoxStyled = styled.div` - display: flex; - flex-direction: column; - margin-top: 10px; - border: 1px solid #ffab20; - position: absolute; - transition: ${({ state }) => 'top 1s, opacity 1.5s, left 1s'}; - top: ${props => (props.top ? `${props.top}px` : 0)}; - left: ${props => (props.active ? `${63}%` : `${65}%`)}; - max-width: 250px; - opacity: ${({ state }) => { - switch (state) { - case 'exited': - return 0.2; - case 'exiting': - return 0.4; - case 'entering': - return 0.6; - case 'entered': - return 1; - default: - } - }}; -`; - -export default ({ comment, top, dataBox }) => { - const [animate, setAnimate] = useState(false); - const { - attrs: { id }, - } = comment; - - useEffect(() => { - setAnimate(true); - }, []); - - const MemorizedComponent = memo(() => { - const { - view, - view: { - main: { - props: { user }, - }, - }, - app, - activeView, - } = useContext(WaxContext); - let active = false; - - const commentPlugin = app.PmPlugins.get('commentPlugin'); - const activeComment = commentPlugin.getState(activeView.state).comment; - - if (activeComment && id === activeComment.attrs.id) active = true; - - const setCommentActive = () => { - const viewId = comment.attrs.viewid; - - if (active) { - view[viewId].focus(); - return false; - } - - const allCommentsWithSameId = DocumentHelpers.findAllMarksWithSameId( - view[viewId].state, - comment, - ); - - const maxPos = maxBy(allCommentsWithSameId, 'pos'); - maxPos.pos += last(allCommentsWithSameId).node.nodeSize; - - view[viewId].dispatch( - view[viewId].state.tr.setSelection( - new TextSelection( - view[viewId].state.tr.doc.resolve(maxPos.pos, maxPos.pos), - ), - ), - ); - - view[viewId].focus(); - }; - - return ( - <> - <Transition in={animate} timeout={1000}> - {state => ( - <CommentBoxStyled - top={top} - state={state} - data-box={dataBox} - active={active} - onClick={setCommentActive} - > - <Comment - comment={comment} - active={active} - activeView={activeView} - user={user} - /> - </CommentBoxStyled> - )} - </Transition> - </> - ); - }); - - return <MemorizedComponent />; -}; diff --git a/wax-prosemirror-components/src/components/link/LinkComponent.js b/wax-prosemirror-components/src/components/link/LinkComponent.js index da2b9e74c0d5b889951cf5c941dab50f94af4643..76ff2dc6a635741cca21048fe23925b844bb1dbd 100644 --- a/wax-prosemirror-components/src/components/link/LinkComponent.js +++ b/wax-prosemirror-components/src/components/link/LinkComponent.js @@ -4,19 +4,39 @@ import styled from 'styled-components'; import { WaxContext } from 'wax-prosemirror-core'; import { DocumentHelpers } from 'wax-prosemirror-utilities'; +const Wrapper = styled.div` + background: silver; + display: inline-block; + padding: 12px; + a { + color: unset; + text-decoration: none; + } +`; + const LinkWrapper = styled.div` - padding: 20px; - border-radius: 3px; - border: 1px solid #000; - background: #ecedf1; - z-index: 9999; - -webkit-box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.75); - -moz-box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.75); - box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.75); + display: inline-block; + width: 250px; + margin-right: 12px; +`; + +const Input = styled.input` + width: calc(100% - 8px); + border: none; + outline: none; + :focus { + outline: none; + } `; -const Button = styled.button` - cursor: pointer; +const ButtonGroup = styled.div` + display: inline-block; +`; + +const StyledButton = styled.button` + margin-right: 10px; + background: #777; + color: #fff; `; const LinkComponent = ({ mark, setPosition, position }) => { @@ -26,9 +46,9 @@ const LinkComponent = ({ mark, setPosition, position }) => { const { state, dispatch } = activeView; const ref = useRef(null); const linkInput = useRef(null); - const [addButtonText, setButtonText] = useState('Create'); const [lastLinkMark, setLLastLinkMark] = useState(linkMark); const [linkHref, setLinkHref] = useState(href); + const [editable, setEditable] = useState(!linkHref); useEffect(() => { setLinkText(); @@ -37,6 +57,10 @@ const LinkComponent = ({ mark, setPosition, position }) => { const addLinkHref = () => { const href = linkHref; + if (linkInput.current.value === '') { + linkInput.current.focus(); + return false; + } const linkMark = state.schema.marks.link; const { tr } = state; @@ -50,6 +74,7 @@ const LinkComponent = ({ mark, setPosition, position }) => { }), ), ); + setEditable(false); activeView.focus(); }; @@ -74,10 +99,8 @@ const LinkComponent = ({ mark, setPosition, position }) => { const setLinkText = () => { if (mark && mark.attrs.href !== '') { - setButtonText('Update'); setLinkHref(mark.attrs.href); } else { - setButtonText('Create'); setLinkHref(''); if (linkInput.current) linkInput.current.focus(); } @@ -87,7 +110,7 @@ const LinkComponent = ({ mark, setPosition, position }) => { const { selection: { $from, $to }, } = state; - const PMLinkMark = state.schema.marks['link']; + const PMLinkMark = state.schema.marks.link; const actualMark = DocumentHelpers.findMark(state, PMLinkMark); setLLastLinkMark(actualMark); @@ -98,29 +121,88 @@ const LinkComponent = ({ mark, setPosition, position }) => { dispatch( state.tr .setMeta('addToHistory', false) - .removeMark( - lastLinkMark.from, - lastLinkMark.to, - state.schema.marks.link, - ), + .removeMark(lastLinkMark.from, lastLinkMark.to, PMLinkMark), ); } }; + const getValidUrl = (url = '') => { + let newUrl = window.decodeURIComponent(url); + newUrl = newUrl.trim().replace(/\s/g, ''); + + if (/^(:\/\/)/.test(newUrl)) { + return `http${newUrl}`; + } + if (!/^(f|ht)tps?:\/\//i.test(newUrl)) { + return `http://${newUrl}`; + } + + return newUrl; + }; + + const editLinkHref = () => { + if ( + linkInput.current && + linkInput.current.value === '' && + lastLinkMark.attrs.href === '' + ) { + dispatch( + state.tr + .setMeta('addToHistory', false) + .removeMark(mark.from, mark.to, state.schema.marks.link), + ); + return false; + } + setLinkHref(lastLinkMark.attrs.href); + setEditable(!editable); + return false; + }; + return mark ? ( - <LinkWrapper ref={ref}> - <input - type="text" - ref={linkInput} - onChange={updateLinkHref} - onKeyPress={handleKeyDown} - value={linkHref} - /> - <Button primary onClick={addLinkHref}> - {addButtonText} - </Button> - <Button onClick={removeLink}>Remove</Button> - </LinkWrapper> + <Wrapper> + <LinkWrapper ref={ref}> + {editable && ( + <Input + ref={linkInput} + onChange={updateLinkHref} + onKeyPress={handleKeyDown} + value={linkHref} + /> + )} + + {!editable && ( + <a href={getValidUrl(linkHref)} rel="noreferrer" target="_blank"> + {getValidUrl(linkHref)} + </a> + )} + </LinkWrapper> + + <ButtonGroup> + {editable && ( + <> + <StyledButton onClick={addLinkHref} type="button"> + Apply + </StyledButton> + + <StyledButton onClick={editLinkHref} type="button"> + Cancel + </StyledButton> + </> + )} + + {!editable && ( + <> + <StyledButton onClick={editLinkHref} type="button"> + Edit + </StyledButton> + + <StyledButton onClick={removeLink} type="button"> + Remove + </StyledButton> + </> + )} + </ButtonGroup> + </Wrapper> ) : null; }; diff --git a/wax-prosemirror-components/src/ui/Link/LinkTooltip.js b/wax-prosemirror-components/src/ui/Link/LinkTooltip.js deleted file mode 100644 index 63b7d764c48422c8a595e4dbf2199dacb1333ef2..0000000000000000000000000000000000000000 --- a/wax-prosemirror-components/src/ui/Link/LinkTooltip.js +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; - -const Wrapper = styled.div` - background: silver; - display: inline-block; - padding: 12px; - - a { - color: unset; - text-decoration: none; - } -`; - -const LinkWrapper = styled.div` - display: inline-block; - width: 250px; - margin-right: 12px; -`; - -const Input = styled.input` - width: calc(100% - 8px); -`; - -const ButtonGroup = styled.div` - display: inline-block; -`; - -const Link = props => { - const { className, onClickApply, onClickRemove, value } = props; - - const [editable, setEditable] = useState(!value); - const [inputValue, setInputValue] = useState(value || ''); - const [valueIfCancelled, setValueIfCancelled] = useState(''); - - const handleChange = e => { - setInputValue(e.target.value); - }; - - const handleApply = () => { - if (inputValue) { - onClickApply(inputValue); - setEditable(false); - } - }; - - const handleEdit = e => { - setEditable(true); - setValueIfCancelled(inputValue); - }; - - const handleCancel = () => { - if (valueIfCancelled) { - setInputValue(valueIfCancelled); - setValueIfCancelled(null); - setEditable(false); - } else { - handleRemove(); - } - }; - - const handleRemove = e => { - onClickRemove(); - }; - - const handleKeyUp = e => { - if (e.keyCode === 13) handleApply(); // enter - if (e.keyCode === 27) handleCancel(); // esc - }; - - return ( - <Wrapper className={className}> - <LinkWrapper> - {editable && ( - <Input - onChange={handleChange} - onKeyUp={handleKeyUp} - value={inputValue} - /> - )} - - {!editable && ( - <a href={value} rel="noreferrer" target="_blank"> - {inputValue} - </a> - )} - </LinkWrapper> - - <ButtonGroup> - {editable && ( - <> - <button onClick={handleApply} type="button"> - Apply - </button> - - <button onClick={handleCancel} type="button"> - Cancel - </button> - </> - )} - - {!editable && ( - <> - <button onClick={handleEdit} type="button"> - Edit - </button> - - <button onClick={handleRemove} type="button"> - Remove - </button> - </> - )} - </ButtonGroup> - </Wrapper> - ); -}; - -Link.propTypes = { - onClickApply: PropTypes.func.isRequired, - onClickRemove: PropTypes.func.isRequired, - value: PropTypes.string, -}; - -Link.defaultProps = { - value: null, -}; - -export default Link;