From f72fa40a0f5c8a02901f925461ad653c836b0848 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 3 Apr 2018 20:20:30 -0700 Subject: [PATCH] WIP --- app/components/Editor/Editor.js | 351 ------------------ app/components/Editor/changes.js | 93 ----- .../Editor/components/BlockInsert.js | 166 --------- .../Editor/components/ClickablePadding.js | 22 -- app/components/Editor/components/Code.js | 52 --- app/components/Editor/components/Contents.js | 163 -------- .../Editor/components/CopyButton.js | 51 --- app/components/Editor/components/Heading.js | 97 ----- .../Editor/components/HorizontalRule.js | 22 -- app/components/Editor/components/Image.js | 104 ------ .../Editor/components/InlineCode.js | 14 - app/components/Editor/components/Link.js | 51 --- app/components/Editor/components/ListItem.js | 22 -- app/components/Editor/components/Paragraph.js | 35 -- .../Editor/components/Placeholder.js | 11 - app/components/Editor/components/TodoItem.js | 51 --- app/components/Editor/components/TodoList.js | 13 - .../Editor/components/Toolbar/BlockToolbar.js | 220 ----------- .../Editor/components/Toolbar/Toolbar.js | 186 ---------- .../Toolbar/components/DocumentResult.js | 51 --- .../Toolbar/components/FormattingToolbar.js | 115 ------ .../Toolbar/components/LinkToolbar.js | 231 ------------ .../Toolbar/components/ToolbarButton.js | 26 -- .../Editor/components/Toolbar/index.js | 3 - app/components/Editor/headingToSlug.js | 25 -- app/components/Editor/index.js | 3 - app/components/Editor/marks.js | 27 -- app/components/Editor/nodes.js | 77 ---- app/components/Editor/plugins.js | 55 --- app/components/Editor/plugins/EditList.js | 8 - .../Editor/plugins/KeyboardShortcuts.js | 35 -- .../Editor/plugins/MarkdownShortcuts.js | 287 -------------- app/components/Editor/schema.js | 75 ---- app/components/Editor/serializer.js | 3 - app/components/Editor/types.js | 19 - app/components/Editor/utils.js | 11 - app/scenes/Document/Document.js | 8 +- package.json | 1 + yarn.lock | 4 + 39 files changed, 10 insertions(+), 2778 deletions(-) delete mode 100644 app/components/Editor/Editor.js delete mode 100644 app/components/Editor/changes.js delete mode 100644 app/components/Editor/components/BlockInsert.js delete mode 100644 app/components/Editor/components/ClickablePadding.js delete mode 100644 app/components/Editor/components/Code.js delete mode 100644 app/components/Editor/components/Contents.js delete mode 100644 app/components/Editor/components/CopyButton.js delete mode 100644 app/components/Editor/components/Heading.js delete mode 100644 app/components/Editor/components/HorizontalRule.js delete mode 100644 app/components/Editor/components/Image.js delete mode 100644 app/components/Editor/components/InlineCode.js delete mode 100644 app/components/Editor/components/Link.js delete mode 100644 app/components/Editor/components/ListItem.js delete mode 100644 app/components/Editor/components/Paragraph.js delete mode 100644 app/components/Editor/components/Placeholder.js delete mode 100644 app/components/Editor/components/TodoItem.js delete mode 100644 app/components/Editor/components/TodoList.js delete mode 100644 app/components/Editor/components/Toolbar/BlockToolbar.js delete mode 100644 app/components/Editor/components/Toolbar/Toolbar.js delete mode 100644 app/components/Editor/components/Toolbar/components/DocumentResult.js delete mode 100644 app/components/Editor/components/Toolbar/components/FormattingToolbar.js delete mode 100644 app/components/Editor/components/Toolbar/components/LinkToolbar.js delete mode 100644 app/components/Editor/components/Toolbar/components/ToolbarButton.js delete mode 100644 app/components/Editor/components/Toolbar/index.js delete mode 100644 app/components/Editor/headingToSlug.js delete mode 100644 app/components/Editor/index.js delete mode 100644 app/components/Editor/marks.js delete mode 100644 app/components/Editor/nodes.js delete mode 100644 app/components/Editor/plugins.js delete mode 100644 app/components/Editor/plugins/EditList.js delete mode 100644 app/components/Editor/plugins/KeyboardShortcuts.js delete mode 100644 app/components/Editor/plugins/MarkdownShortcuts.js delete mode 100644 app/components/Editor/schema.js delete mode 100644 app/components/Editor/serializer.js delete mode 100644 app/components/Editor/types.js delete mode 100644 app/components/Editor/utils.js diff --git a/app/components/Editor/Editor.js b/app/components/Editor/Editor.js deleted file mode 100644 index 1cd81772..00000000 --- a/app/components/Editor/Editor.js +++ /dev/null @@ -1,351 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import { observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { Value, Change } from 'slate'; -import { Editor } from 'slate-react'; -import styled from 'styled-components'; -import breakpoint from 'styled-components-breakpoint'; -import keydown from 'react-keydown'; -import type { SlateNodeProps, Plugin } from './types'; -import getDataTransferFiles from 'utils/getDataTransferFiles'; -import Flex from 'shared/components/Flex'; -import ClickablePadding from './components/ClickablePadding'; -import Toolbar from './components/Toolbar'; -import BlockInsert from './components/BlockInsert'; -import Placeholder from './components/Placeholder'; -import Contents from './components/Contents'; -import Markdown from './serializer'; -import createPlugins from './plugins'; -import { insertImageFile } from './changes'; -import renderMark from './marks'; -import createRenderNode from './nodes'; -import schema from './schema'; -import { isModKey } from './utils'; - -type Props = { - text: string, - onChange: Change => *, - onSave: ({ redirect?: boolean, publish?: boolean }) => *, - onCancel: () => void, - onImageUploadStart: () => void, - onImageUploadStop: () => void, - emoji?: string, - readOnly: boolean, -}; - -@observer -class MarkdownEditor extends Component { - props: Props; - editor: Editor; - renderNode: SlateNodeProps => *; - plugins: Plugin[]; - @observable editorValue: Value; - @observable editorLoaded: boolean = false; - - constructor(props: Props) { - super(props); - - this.renderNode = createRenderNode({ - onInsertImage: this.insertImageFile, - }); - this.plugins = createPlugins({ - onImageUploadStart: props.onImageUploadStart, - onImageUploadStop: props.onImageUploadStop, - }); - - this.editorValue = Markdown.deserialize(props.text); - } - - componentDidMount() { - if (this.props.readOnly) return; - if (this.props.text) { - this.focusAtEnd(); - } else { - this.focusAtStart(); - } - } - - componentDidUpdate(prevProps: Props) { - if (prevProps.readOnly && !this.props.readOnly) { - this.focusAtEnd(); - } - } - - setEditorRef = (ref: Editor) => { - this.editor = ref; - // Force re-render to show ToC () - this.editorLoaded = true; - }; - - onChange = (change: Change) => { - if (this.editorValue !== change.value) { - this.props.onChange(Markdown.serialize(change.value)); - this.editorValue = change.value; - } - }; - - handleDrop = async (ev: SyntheticEvent) => { - if (this.props.readOnly) return; - // check if this event was already handled by the Editor - if (ev.isDefaultPrevented()) return; - - // otherwise we'll handle this - ev.preventDefault(); - ev.stopPropagation(); - - const files = getDataTransferFiles(ev); - for (const file of files) { - if (file.type.startsWith('image/')) { - await this.insertImageFile(file); - } - } - }; - - insertImageFile = (file: window.File) => { - this.editor.change(change => - change.call( - insertImageFile, - file, - this.editor, - this.props.onImageUploadStart, - this.props.onImageUploadStop - ) - ); - }; - - cancelEvent = (ev: SyntheticEvent) => { - ev.preventDefault(); - }; - - // Handling of keyboard shortcuts outside of editor focus - @keydown('meta+s') - onSave(ev: SyntheticKeyboardEvent) { - if (this.props.readOnly) return; - - ev.preventDefault(); - ev.stopPropagation(); - this.props.onSave({ redirect: false }); - } - - @keydown('meta+enter') - onSaveAndExit(ev: SyntheticKeyboardEvent) { - if (this.props.readOnly) return; - - ev.preventDefault(); - ev.stopPropagation(); - this.props.onSave({ redirect: true }); - } - - @keydown('esc') - onCancel() { - if (this.props.readOnly) return; - this.props.onCancel(); - } - - // Handling of keyboard shortcuts within editor focus - onKeyDown = (ev: SyntheticKeyboardEvent, change: Change) => { - if (!isModKey(ev)) return; - - switch (ev.key) { - case 's': - this.onSave(ev); - return change; - case 'Enter': - this.onSaveAndExit(ev); - return change; - case 'Escape': - this.onCancel(); - return change; - default: - } - }; - - focusAtStart = () => { - this.editor.change(change => - change.collapseToStartOf(change.value.document).focus() - ); - }; - - focusAtEnd = () => { - this.editor.change(change => - change.collapseToEndOf(change.value.document).focus() - ); - }; - - render = () => { - const { readOnly, emoji, onSave } = this.props; - - return ( - - -
- {readOnly && - this.editorLoaded && - this.editor && } - {!readOnly && - this.editor && ( - - )} - {!readOnly && - this.editor && ( - - )} - - - - - ); - }; -} - -const MaxWidth = styled(Flex)` - padding: 0 20px; - max-width: 100vw; - height: 100%; - - ${breakpoint('tablet')` - padding: 0; - margin: 0 60px; - max-width: 46em; - `}; -`; - -const Header = styled(Flex)` - height: 60px; - flex-shrink: 0; - align-items: flex-end; - ${({ readOnly }) => !readOnly && 'cursor: text;'}; - - @media print { - display: none; - } -`; - -const StyledEditor = styled(Editor)` - font-weight: 400; - font-size: 1em; - line-height: 1.7em; - width: 100%; - color: #1b2830; - - h1, - h2, - h3, - h4, - h5, - h6 { - font-weight: 500; - } - - h1:first-of-type { - ${Placeholder} { - visibility: visible; - } - } - - p:nth-child(2) { - ${Placeholder} { - visibility: visible; - } - } - - ul, - ol { - margin: 0 0.1em; - padding-left: 1em; - - ul, - ol { - margin: 0.1em; - } - } - - p { - position: relative; - margin: 0; - } - - a:hover { - text-decoration: ${({ readOnly }) => (readOnly ? 'underline' : 'none')}; - } - - li p { - display: inline; - margin: 0; - } - - .todoList { - list-style: none; - padding-left: 0; - - .todoList { - padding-left: 1em; - } - } - - .todo { - span:last-child:focus { - outline: none; - } - } - - blockquote { - border-left: 3px solid #efefef; - margin: 0; - padding-left: 10px; - font-style: italic; - } - - table { - border-collapse: collapse; - } - - tr { - border-bottom: 1px solid #eee; - } - - th { - font-weight: bold; - } - - th, - td { - padding: 5px 20px 5px 0; - } - - b, - strong { - font-weight: 600; - } -`; - -export default MarkdownEditor; diff --git a/app/components/Editor/changes.js b/app/components/Editor/changes.js deleted file mode 100644 index b44c128c..00000000 --- a/app/components/Editor/changes.js +++ /dev/null @@ -1,93 +0,0 @@ -// @flow -import { Change } from 'slate'; -import { Editor } from 'slate-react'; -import uuid from 'uuid'; -import EditList from './plugins/EditList'; -import { uploadFile } from 'utils/uploadFile'; - -const { changes } = EditList; - -type Options = { - type: string | Object, - wrapper?: string | Object, -}; - -export function splitAndInsertBlock(change: Change, options: Options) { - const { type, wrapper } = options; - const parent = change.value.document.getParent(change.value.startBlock.key); - - // lists get some special treatment - if (parent && parent.type === 'list-item') { - change - .collapseToStart() - .call(changes.splitListItem) - .collapseToEndOfPreviousBlock() - .call(changes.unwrapList); - } - - if (wrapper) change.collapseToStartOfNextBlock(); - - // this is a hack as insertBlock with normalize: false does not appear to work - change.insertBlock('paragraph').setBlock(type, { normalize: false }); - - if (wrapper) change.wrapBlock(wrapper); - return change; -} - -export async function insertImageFile( - change: Change, - file: window.File, - editor: Editor, - onImageUploadStart: () => void, - onImageUploadStop: () => void -) { - onImageUploadStart(); - try { - // load the file as a data URL - const id = uuid.v4(); - const alt = ''; - const reader = new FileReader(); - reader.addEventListener('load', () => { - const src = reader.result; - const node = { - type: 'image', - isVoid: true, - data: { src, id, alt, loading: true }, - }; - - // insert / replace into document as uploading placeholder replacing - // empty paragraphs if available. - if ( - !change.value.startBlock.text && - change.value.startBlock.type === 'paragraph' - ) { - change.setBlock(node); - } else { - change.insertBlock(node); - } - - editor.onChange(change); - }); - reader.readAsDataURL(file); - - // now we have a placeholder, start the upload - const asset = await uploadFile(file); - const src = asset.url; - - // we dont use the original change provided to the callback here - // as the state may have changed significantly in the time it took to - // upload the file. - const placeholder = editor.value.document.findDescendant( - node => node.data && node.data.get('id') === id - ); - - change.setNodeByKey(placeholder.key, { - data: { src, alt, loading: false }, - }); - editor.onChange(change); - } catch (err) { - throw err; - } finally { - onImageUploadStop(); - } -} diff --git a/app/components/Editor/components/BlockInsert.js b/app/components/Editor/components/BlockInsert.js deleted file mode 100644 index 71d2398e..00000000 --- a/app/components/Editor/components/BlockInsert.js +++ /dev/null @@ -1,166 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import { Portal } from 'react-portal'; -import { Node } from 'slate'; -import { Editor, findDOMNode } from 'slate-react'; -import { observable } from 'mobx'; -import { observer } from 'mobx-react'; -import styled from 'styled-components'; -import { color } from 'shared/styles/constants'; -import PlusIcon from 'components/Icon/PlusIcon'; - -type Props = { - editor: Editor, -}; - -function findClosestRootNode(value, ev) { - let previous; - - for (const node of value.document.nodes) { - const element = findDOMNode(node); - const bounds = element.getBoundingClientRect(); - if (bounds.top > ev.clientY) return previous; - previous = { node, element, bounds }; - } - - return previous; -} - -@observer -export default class BlockInsert extends Component { - props: Props; - mouseMoveTimeout: number; - mouseMovementSinceClick: number = 0; - lastClientX: number = 0; - lastClientY: number = 0; - - @observable closestRootNode: Node; - @observable active: boolean = false; - @observable top: number; - @observable left: number; - - componentDidMount = () => { - window.addEventListener('mousemove', this.handleMouseMove); - }; - - componentWillUnmount = () => { - window.removeEventListener('mousemove', this.handleMouseMove); - }; - - setInactive = () => { - this.active = false; - }; - - handleMouseMove = (ev: SyntheticMouseEvent) => { - const windowWidth = window.innerWidth / 2.5; - const result = findClosestRootNode(this.props.editor.value, ev); - const movementThreshold = 200; - - this.mouseMovementSinceClick += - Math.abs(this.lastClientX - ev.clientX) + - Math.abs(this.lastClientY - ev.clientY); - this.lastClientX = ev.clientX; - this.lastClientY = ev.clientY; - - this.active = - ev.clientX < windowWidth && - this.mouseMovementSinceClick > movementThreshold; - - if (result) { - this.closestRootNode = result.node; - - // do not show block menu on title heading or editor - const firstNode = this.props.editor.value.document.nodes.first(); - if ( - result.node === firstNode || - result.node.type === 'block-toolbar' || - !!result.node.text.trim() - ) { - this.left = -1000; - } else { - this.left = Math.round(result.bounds.left - 20); - this.top = Math.round(result.bounds.top + window.scrollY); - } - } - - if (this.active) { - clearTimeout(this.mouseMoveTimeout); - this.mouseMoveTimeout = setTimeout(this.setInactive, 2000); - } - }; - - handleClick = (ev: SyntheticMouseEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - - this.mouseMovementSinceClick = 0; - this.active = false; - - const { editor } = this.props; - - editor.change(change => { - // remove any existing toolbars in the document as a fail safe - editor.value.document.nodes.forEach(node => { - if (node.type === 'block-toolbar') { - change.removeNodeByKey(node.key); - } - }); - - change.collapseToStartOf(this.closestRootNode); - - // if we're on an empty paragraph then just replace it with the block - // toolbar. Otherwise insert the toolbar as an extra Node. - if ( - !this.closestRootNode.text.trim() && - this.closestRootNode.type === 'paragraph' - ) { - change.setNodeByKey(this.closestRootNode.key, { - type: 'block-toolbar', - isVoid: true, - }); - } - }); - }; - - render() { - const style = { top: `${this.top}px`, left: `${this.left}px` }; - - return ( - - - - - - ); - } -} - -const Trigger = styled.div` - position: absolute; - z-index: 1; - opacity: 0; - background-color: ${color.white}; - transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275), - transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275); - line-height: 0; - margin-left: -10px; - box-shadow: inset 0 0 0 2px ${color.slate}; - border-radius: 100%; - transform: scale(0.9); - cursor: pointer; - - &:hover { - background-color: ${color.slate}; - - svg { - fill: ${color.white}; - } - } - - ${({ active }) => - active && - ` - transform: scale(1); - opacity: .9; - `}; -`; diff --git a/app/components/Editor/components/ClickablePadding.js b/app/components/Editor/components/ClickablePadding.js deleted file mode 100644 index 55759175..00000000 --- a/app/components/Editor/components/ClickablePadding.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow -import React from 'react'; -import styled from 'styled-components'; - -type Props = { - onClick?: ?Function, - grow?: boolean, -}; - -const ClickablePadding = (props: Props) => { - return ; -}; - -const Container = styled.div` - min-height: 50vh; - padding-top: 50px; - cursor: ${({ onClick }) => (onClick ? 'text' : 'default')}; - - ${({ grow }) => grow && `flex-grow: 1;`}; -`; - -export default ClickablePadding; diff --git a/app/components/Editor/components/Code.js b/app/components/Editor/components/Code.js deleted file mode 100644 index 392237df..00000000 --- a/app/components/Editor/components/Code.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow -import React from 'react'; -import styled from 'styled-components'; -import type { SlateNodeProps } from '../types'; -import CopyButton from './CopyButton'; -import { color } from 'shared/styles/constants'; - -function getCopyText(node) { - return node.nodes.reduce((memo, line) => `${memo}${line.text}\n`, ''); -} - -export default function Code({ - children, - node, - readOnly, - attributes, -}: SlateNodeProps) { - // TODO: There is a currently a bug in slate-prism that prevents code elements - // with a language class name from formatting correctly on first load. - // const language = node.data.get('language') || 'javascript'; - - return ( - - {readOnly && } - {children} - - ); -} - -const Container = styled.div` - position: relative; - background: ${color.smokeLight}; - border-radius: 4px; - border: 1px solid ${color.smokeDark}; - - code { - display: block; - overflow-x: scroll; - padding: 0.5em 1em; - line-height: 1.4em; - } - - pre { - margin: 0; - } - - &:hover { - > span { - opacity: 1; - } - } -`; diff --git a/app/components/Editor/components/Contents.js b/app/components/Editor/components/Contents.js deleted file mode 100644 index 87f01ae9..00000000 --- a/app/components/Editor/components/Contents.js +++ /dev/null @@ -1,163 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import styled from 'styled-components'; -import breakpoint from 'styled-components-breakpoint'; -import { observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { Editor } from 'slate-react'; -import { Block } from 'slate'; -import { List } from 'immutable'; -import { color } from 'shared/styles/constants'; -import headingToSlug from '../headingToSlug'; - -type Props = { - editor: Editor, -}; - -@observer -class Contents extends Component { - props: Props; - @observable activeHeading: ?string; - - componentDidMount() { - window.addEventListener('scroll', this.updateActiveHeading); - this.updateActiveHeading(); - } - - componentWillUnmount() { - window.removeEventListener('scroll', this.updateActiveHeading); - } - - updateActiveHeading = () => { - const elements = this.headingElements; - if (!elements.length) return; - - let activeHeading = elements[0].id; - - for (const element of elements) { - const bounds = element.getBoundingClientRect(); - if (bounds.top <= 0) activeHeading = element.id; - } - - this.activeHeading = activeHeading; - }; - - get headingElements(): HTMLElement[] { - const elements = []; - const tagNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; - - for (const tagName of tagNames) { - for (const ele of document.getElementsByTagName(tagName)) { - elements.push(ele); - } - } - - return elements; - } - - get headings(): List { - const { editor } = this.props; - - return editor.value.document.nodes.filter((node: Block) => { - if (!node.text) return false; - return node.type.match(/^heading/); - }); - } - - render() { - const { editor } = this.props; - - // If there are one or less headings in the document no need for a minimap - if (this.headings.size <= 1) return null; - - return ( - - - {this.headings.map(heading => { - const slug = headingToSlug(editor.value.document, heading); - const active = this.activeHeading === slug; - - return ( - - - {heading.text} - - - ); - })} - - - ); - } -} - -const Wrapper = styled.div` - display: none; - position: fixed; - right: 0; - top: 150px; - z-index: 100; - - @media print { - display: none; - } - - ${breakpoint('tablet')` - display: block; - `}; -`; - -const Anchor = styled.a` - color: ${props => (props.active ? color.slateDark : color.slate)}; - font-weight: ${props => (props.active ? 500 : 400)}; - opacity: 0; - transition: all 100ms ease-in-out; - margin-right: -5px; - padding: 2px 0; - pointer-events: none; - text-overflow: ellipsis; - - &:hover { - color: ${color.primary}; - } -`; - -const ListItem = styled.li` - position: relative; - margin-left: ${props => (props.type.match(/heading[12]/) ? '8px' : '16px')}; - text-align: right; - color: ${color.slate}; - padding-right: 16px; - white-space: nowrap; - - &:after { - color: ${props => (props.active ? color.slateDark : color.slate)}; - content: "${props => (props.type.match(/heading[12]/) ? '—' : '–')}"; - position: absolute; - right: 0; - } -`; - -const Sections = styled.ol` - margin: 0 0 0 -8px; - padding: 0; - list-style: none; - font-size: 13px; - width: 100px; - transition-delay: 1s; - transition: width 100ms ease-in-out; - - &:hover { - width: 300px; - transition-delay: 0s; - - ${Anchor} { - opacity: 1; - margin-right: 0; - background: ${color.white}; - pointer-events: all; - } - } -`; - -export default Contents; diff --git a/app/components/Editor/components/CopyButton.js b/app/components/Editor/components/CopyButton.js deleted file mode 100644 index 7f9acee0..00000000 --- a/app/components/Editor/components/CopyButton.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import { observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { color } from 'shared/styles/constants'; -import styled from 'styled-components'; -import CopyToClipboard from 'components/CopyToClipboard'; - -@observer -class CopyButton extends Component { - @observable copied: boolean = false; - copiedTimeout: ?number; - - componentWillUnmount() { - clearTimeout(this.copiedTimeout); - } - - handleCopy = () => { - this.copied = true; - this.copiedTimeout = setTimeout(() => (this.copied = false), 3000); - }; - - render() { - return ( - - {this.copied ? 'Copied!' : 'Copy'} - - ); - } -} - -const StyledCopyToClipboard = styled(CopyToClipboard)` - position: absolute; - top: 0; - right: 0; - - opacity: 0; - transition: opacity 50ms ease-in-out; - z-index: 1; - font-size: 12px; - background: ${color.smoke}; - border-radius: 0 2px 0 2px; - padding: 1px 6px; - cursor: pointer; - - &:hover { - background: ${color.smokeDark}; - } -`; - -export default CopyButton; diff --git a/app/components/Editor/components/Heading.js b/app/components/Editor/components/Heading.js deleted file mode 100644 index 32f38d6b..00000000 --- a/app/components/Editor/components/Heading.js +++ /dev/null @@ -1,97 +0,0 @@ -// @flow -import React from 'react'; -import { Document } from 'slate'; -import type { SlateNodeProps } from '../types'; -import styled from 'styled-components'; -import headingToSlug from '../headingToSlug'; -import Placeholder from './Placeholder'; - -type Props = SlateNodeProps & { - component: string, - className: string, - placeholder: string, -}; - -function Heading(props: Props) { - const { - parent, - placeholder, - node, - editor, - readOnly, - children, - component = 'h1', - className, - attributes, - } = props; - const parentIsDocument = parent instanceof Document; - const firstHeading = parentIsDocument && parent.nodes.first() === node; - const showPlaceholder = placeholder && firstHeading && !node.text; - const slugish = headingToSlug(editor.value.document, node); - const showHash = readOnly && !!slugish; - const Component = component; - const emoji = editor.props.emoji || ''; - const title = node.text.trim(); - const startsWithEmojiAndSpace = - emoji && title.match(new RegExp(`^${emoji}\\s`)); - - return ( - - {children} - {showPlaceholder && ( - - {editor.props.placeholder} - - )} - {showHash && ( - - # - - )} - - ); -} - -const Wrapper = styled.div` - display: inline; - margin-left: ${(props: SlateNodeProps) => (props.hasEmoji ? '-1.2em' : 0)}; -`; - -const Anchor = styled.a` - visibility: hidden; - padding-left: 0.25em; - color: #dedede; - - &:hover { - color: #cdcdcd; - } -`; - -export const StyledHeading = styled(Heading)` - position: relative; - - &:hover { - ${Anchor} { - visibility: visible; - text-decoration: none; - } - } -`; -export const Heading1 = (props: SlateNodeProps) => ( - -); -export const Heading2 = (props: SlateNodeProps) => ( - -); -export const Heading3 = (props: SlateNodeProps) => ( - -); -export const Heading4 = (props: SlateNodeProps) => ( - -); -export const Heading5 = (props: SlateNodeProps) => ( - -); -export const Heading6 = (props: SlateNodeProps) => ( - -); diff --git a/app/components/Editor/components/HorizontalRule.js b/app/components/Editor/components/HorizontalRule.js deleted file mode 100644 index 4afd1944..00000000 --- a/app/components/Editor/components/HorizontalRule.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow -import React from 'react'; -import styled from 'styled-components'; -import type { SlateNodeProps } from '../types'; -import { color } from 'shared/styles/constants'; - -function HorizontalRule(props: SlateNodeProps) { - const { editor, node, attributes } = props; - const active = - editor.value.isFocused && editor.value.selection.hasEdgeIn(node); - return ; -} - -const StyledHr = styled.hr` - padding-top: 0.75em; - margin: 0; - border: 0; - border-bottom: 1px solid - ${props => (props.active ? color.slate : color.slateLight)}; -`; - -export default HorizontalRule; diff --git a/app/components/Editor/components/Image.js b/app/components/Editor/components/Image.js deleted file mode 100644 index a53e4b89..00000000 --- a/app/components/Editor/components/Image.js +++ /dev/null @@ -1,104 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import ImageZoom from 'react-medium-image-zoom'; -import styled from 'styled-components'; -import type { SlateNodeProps } from '../types'; -import { color } from 'shared/styles/constants'; - -class Image extends Component { - props: SlateNodeProps; - - handleChange = (ev: SyntheticInputEvent) => { - const alt = ev.target.value; - const { editor, node } = this.props; - const data = node.data.toObject(); - - editor.change(change => - change.setNodeByKey(node.key, { data: { ...data, alt } }) - ); - }; - - handleClick = (ev: SyntheticInputEvent) => { - ev.stopPropagation(); - }; - - render() { - const { attributes, editor, node, readOnly } = this.props; - const loading = node.data.get('loading'); - const caption = node.data.get('alt'); - const src = node.data.get('src'); - const active = - editor.value.isFocused && editor.value.selection.hasEdgeIn(node); - const showCaption = !readOnly || caption; - - return ( - - {!readOnly ? ( - - ) : ( - - )} - {showCaption && ( - - )} - - ); - } -} - -const StyledImg = styled.img` - max-width: 100%; - box-shadow: ${props => (props.active ? `0 0 0 2px ${color.slate}` : '0')}; - border-radius: ${props => (props.active ? `2px` : '0')}; - opacity: ${props => (props.loading ? 0.5 : 1)}; -`; - -const CenteredImage = styled.span` - display: block; - text-align: center; -`; - -const Caption = styled.input` - border: 0; - display: block; - font-size: 13px; - font-style: italic; - color: ${color.slate}; - padding: 2px 0; - line-height: 16px; - text-align: center; - width: 100%; - outline: none; - background: none; - - &::placeholder { - color: ${color.slate}; - } -`; - -export default Image; diff --git a/app/components/Editor/components/InlineCode.js b/app/components/Editor/components/InlineCode.js deleted file mode 100644 index 1e87d286..00000000 --- a/app/components/Editor/components/InlineCode.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import styled from 'styled-components'; -import { color } from 'shared/styles/constants'; - -const InlineCode = styled.code.attrs({ - spellCheck: false, -})` - padding: 0.25em; - background: ${color.smoke}; - border-radius: 4px; - border: 1px solid ${color.smokeDark}; -`; - -export default InlineCode; diff --git a/app/components/Editor/components/Link.js b/app/components/Editor/components/Link.js deleted file mode 100644 index 57a99feb..00000000 --- a/app/components/Editor/components/Link.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import React from 'react'; -import { Link as InternalLink } from 'react-router-dom'; -import type { SlateNodeProps } from '../types'; - -function getPathFromUrl(href: string) { - if (href[0] === '/') return href; - - try { - const parsed = new URL(href); - return parsed.pathname; - } catch (err) { - return ''; - } -} - -function isInternalUrl(href: string) { - if (href[0] === '/') return true; - - try { - const outline = new URL(BASE_URL); - const parsed = new URL(href); - return parsed.hostname === outline.hostname; - } catch (err) { - return false; - } -} - -export default function Link({ - attributes, - node, - children, - readOnly, -}: SlateNodeProps) { - const href = node.data.get('href'); - const path = getPathFromUrl(href); - - if (isInternalUrl(href) && readOnly) { - return ( - - {children} - - ); - } else { - return ( - - {children} - - ); - } -} diff --git a/app/components/Editor/components/ListItem.js b/app/components/Editor/components/ListItem.js deleted file mode 100644 index 227a3ad1..00000000 --- a/app/components/Editor/components/ListItem.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow -import React from 'react'; -import type { SlateNodeProps } from '../types'; -import TodoItem from './TodoItem'; - -export default function ListItem({ - children, - node, - attributes, - ...props -}: SlateNodeProps) { - const checked = node.data.get('checked'); - - if (checked !== undefined) { - return ( - - {children} - - ); - } - return
  • {children}
  • ; -} diff --git a/app/components/Editor/components/Paragraph.js b/app/components/Editor/components/Paragraph.js deleted file mode 100644 index 952ae174..00000000 --- a/app/components/Editor/components/Paragraph.js +++ /dev/null @@ -1,35 +0,0 @@ -// @flow -import React from 'react'; -import { Document } from 'slate'; -import type { SlateNodeProps } from '../types'; -import Placeholder from './Placeholder'; - -export default function Link({ - attributes, - editor, - node, - parent, - children, - readOnly, -}: SlateNodeProps) { - const parentIsDocument = parent instanceof Document; - const firstParagraph = parent && parent.nodes.get(1) === node; - const lastParagraph = parent && parent.nodes.last() === node; - const showPlaceholder = - !readOnly && - parentIsDocument && - firstParagraph && - lastParagraph && - !node.text; - - return ( -

    - {children} - {showPlaceholder && ( - - {editor.props.bodyPlaceholder} - - )} -

    - ); -} diff --git a/app/components/Editor/components/Placeholder.js b/app/components/Editor/components/Placeholder.js deleted file mode 100644 index c98ef16b..00000000 --- a/app/components/Editor/components/Placeholder.js +++ /dev/null @@ -1,11 +0,0 @@ -// @flow -import styled from 'styled-components'; - -export default styled.span` - position: absolute; - top: 0; - visibility: hidden; - pointer-events: none; - user-select: none; - color: #b1becc; -`; diff --git a/app/components/Editor/components/TodoItem.js b/app/components/Editor/components/TodoItem.js deleted file mode 100644 index f2e5b77e..00000000 --- a/app/components/Editor/components/TodoItem.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import styled from 'styled-components'; -import { color } from 'shared/styles/constants'; -import type { SlateNodeProps } from '../types'; - -export default class TodoItem extends Component { - props: SlateNodeProps; - - handleChange = (ev: SyntheticInputEvent) => { - const checked = ev.target.checked; - const { editor, node } = this.props; - editor.change(change => - change.setNodeByKey(node.key, { data: { checked } }) - ); - }; - - render() { - const { children, node, attributes, readOnly } = this.props; - const checked = node.data.get('checked'); - - return ( - - - {children} - - ); - } -} - -const ListItem = styled.li` - padding-left: 1.4em; - position: relative; - - > p > span { - color: ${props => (props.checked ? color.slateDark : 'inherit')}; - text-decoration: ${props => (props.checked ? 'line-through' : 'none')}; - } -`; - -const Input = styled.input` - position: absolute; - left: 0; - top: 0.4em; -`; diff --git a/app/components/Editor/components/TodoList.js b/app/components/Editor/components/TodoList.js deleted file mode 100644 index 9cf649a6..00000000 --- a/app/components/Editor/components/TodoList.js +++ /dev/null @@ -1,13 +0,0 @@ -// @flow -import styled from 'styled-components'; - -const TodoList = styled.ul` - list-style: none; - padding: 0 !important; - - ul { - padding-left: 1em; - } -`; - -export default TodoList; diff --git a/app/components/Editor/components/Toolbar/BlockToolbar.js b/app/components/Editor/components/Toolbar/BlockToolbar.js deleted file mode 100644 index 6f540e39..00000000 --- a/app/components/Editor/components/Toolbar/BlockToolbar.js +++ /dev/null @@ -1,220 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import { findDOMNode } from 'react-dom'; -import keydown from 'react-keydown'; -import styled from 'styled-components'; -import getDataTransferFiles from 'utils/getDataTransferFiles'; -import Heading1Icon from 'components/Icon/Heading1Icon'; -import Heading2Icon from 'components/Icon/Heading2Icon'; -import BlockQuoteIcon from 'components/Icon/BlockQuoteIcon'; -import ImageIcon from 'components/Icon/ImageIcon'; -import CodeIcon from 'components/Icon/CodeIcon'; -import BulletedListIcon from 'components/Icon/BulletedListIcon'; -import OrderedListIcon from 'components/Icon/OrderedListIcon'; -import HorizontalRuleIcon from 'components/Icon/HorizontalRuleIcon'; -import TodoListIcon from 'components/Icon/TodoListIcon'; -import Flex from 'shared/components/Flex'; -import ToolbarButton from './components/ToolbarButton'; -import type { SlateNodeProps } from '../../types'; -import { color } from 'shared/styles/constants'; -import { fadeIn } from 'shared/styles/animations'; -import { splitAndInsertBlock } from '../../changes'; - -type Props = SlateNodeProps & { - onInsertImage: *, -}; - -type Options = { - type: string | Object, - wrapper?: string | Object, -}; - -class BlockToolbar extends Component { - props: Props; - bar: HTMLDivElement; - file: HTMLInputElement; - - componentDidMount() { - window.addEventListener('click', this.handleOutsideMouseClick); - } - - componentWillUnmount() { - window.removeEventListener('click', this.handleOutsideMouseClick); - } - - handleOutsideMouseClick = (ev: SyntheticMouseEvent) => { - const element = findDOMNode(this.bar); - - if ( - !element || - (ev.target instanceof Node && element.contains(ev.target)) || - (ev.button && ev.button !== 0) - ) { - return; - } - this.removeSelf(ev); - }; - - @keydown('esc') - removeSelf(ev: SyntheticEvent) { - ev.preventDefault(); - ev.stopPropagation(); - - this.props.editor.change(change => - change.setNodeByKey(this.props.node.key, { - type: 'paragraph', - text: '', - isVoid: false, - }) - ); - } - - insertBlock = ( - options: Options, - cursorPosition: 'before' | 'on' | 'after' = 'on' - ) => { - const { editor } = this.props; - - editor.change(change => { - change - .collapseToEndOf(this.props.node) - .call(splitAndInsertBlock, options) - .removeNodeByKey(this.props.node.key) - .collapseToEnd(); - - if (cursorPosition === 'before') change.collapseToStartOfPreviousBlock(); - if (cursorPosition === 'after') change.collapseToStartOfNextBlock(); - return change.focus(); - }); - }; - - handleClickBlock = (ev: SyntheticEvent, type: string) => { - ev.preventDefault(); - - switch (type) { - case 'heading1': - case 'heading2': - case 'block-quote': - case 'code': - return this.insertBlock({ type }); - case 'horizontal-rule': - return this.insertBlock( - { - type: { type: 'horizontal-rule', isVoid: true }, - }, - 'after' - ); - case 'bulleted-list': - return this.insertBlock({ - type: 'list-item', - wrapper: 'bulleted-list', - }); - case 'ordered-list': - return this.insertBlock({ - type: 'list-item', - wrapper: 'ordered-list', - }); - case 'todo-list': - return this.insertBlock({ - type: { type: 'list-item', data: { checked: false } }, - wrapper: 'todo-list', - }); - case 'image': - return this.onPickImage(); - default: - } - }; - - onPickImage = () => { - // simulate a click on the file upload input element - this.file.click(); - }; - - onImagePicked = async (ev: SyntheticEvent) => { - const files = getDataTransferFiles(ev); - for (const file of files) { - await this.props.onInsertImage(file); - } - }; - - renderBlockButton = (type: string, IconClass: Function) => { - return ( - this.handleClickBlock(ev, type)}> - - - ); - }; - - render() { - const { editor, attributes, node } = this.props; - const active = - editor.value.isFocused && editor.value.selection.hasEdgeIn(node); - - return ( - (this.bar = ref)}> - (this.file = ref)} - onChange={this.onImagePicked} - accept="image/*" - /> - {this.renderBlockButton('heading1', Heading1Icon)} - {this.renderBlockButton('heading2', Heading2Icon)} - - {this.renderBlockButton('bulleted-list', BulletedListIcon)} - {this.renderBlockButton('ordered-list', OrderedListIcon)} - {this.renderBlockButton('todo-list', TodoListIcon)} - - {this.renderBlockButton('block-quote', BlockQuoteIcon)} - {this.renderBlockButton('code', CodeIcon)} - {this.renderBlockButton('horizontal-rule', HorizontalRuleIcon)} - {this.renderBlockButton('image', ImageIcon)} - - ); - } -} - -const Separator = styled.div` - height: 100%; - width: 1px; - background: ${color.smokeDark}; - display: inline-block; - margin-left: 10px; -`; - -const Bar = styled(Flex)` - z-index: 100; - animation: ${fadeIn} 150ms ease-in-out; - position: relative; - align-items: center; - background: ${color.smoke}; - height: 44px; - - &:before, - &:after { - content: ''; - position: absolute; - left: -100%; - width: 100%; - height: 44px; - background: ${color.smoke}; - } - - &:after { - left: auto; - right: -100%; - } - - @media print { - display: none; - } -`; - -const HiddenInput = styled.input` - position: absolute; - top: -100px; - left: -100px; - visibility: hidden; -`; - -export default BlockToolbar; diff --git a/app/components/Editor/components/Toolbar/Toolbar.js b/app/components/Editor/components/Toolbar/Toolbar.js deleted file mode 100644 index 703c7b77..00000000 --- a/app/components/Editor/components/Toolbar/Toolbar.js +++ /dev/null @@ -1,186 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import { observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { Portal } from 'react-portal'; -import { Editor, findDOMNode } from 'slate-react'; -import { Node, Value } from 'slate'; -import styled from 'styled-components'; -import FormattingToolbar from './components/FormattingToolbar'; -import LinkToolbar from './components/LinkToolbar'; - -function getLinkInSelection(value): any { - try { - const selectedLinks = value.document - .getInlinesAtRange(value.selection) - .filter(node => node.type === 'link'); - - if (selectedLinks.size) { - const link = selectedLinks.first(); - if (value.selection.hasEdgeIn(link)) return link; - } - } catch (err) { - // It's okay. - } -} - -@observer -export default class Toolbar extends Component { - @observable active: boolean = false; - @observable link: ?Node; - @observable top: string = ''; - @observable left: string = ''; - @observable mouseDown: boolean = false; - - props: { - editor: Editor, - value: Value, - }; - - menu: HTMLElement; - - componentDidMount = () => { - this.update(); - window.addEventListener('mousedown', this.handleMouseDown); - window.addEventListener('mouseup', this.handleMouseUp); - }; - - componentWillUnmount = () => { - window.removeEventListener('mousedown', this.handleMouseDown); - window.removeEventListener('mouseup', this.handleMouseUp); - }; - - componentDidUpdate = () => { - this.update(); - }; - - hideLinkToolbar = () => { - this.link = undefined; - }; - - handleMouseDown = (e: SyntheticMouseEvent) => { - this.mouseDown = true; - }; - - handleMouseUp = (e: SyntheticMouseEvent) => { - this.mouseDown = false; - this.update(); - }; - - showLinkToolbar = (ev: SyntheticEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - - const link = getLinkInSelection(this.props.value); - this.link = link; - }; - - update = () => { - const { value } = this.props; - const link = getLinkInSelection(value); - - if (value.isBlurred || (value.isCollapsed && !link)) { - if (this.active && !this.link) { - this.active = false; - this.link = undefined; - this.top = ''; - this.left = ''; - } - return; - } - - // don't display toolbar for document title - const firstNode = value.document.nodes.first(); - if (firstNode === value.startBlock) return; - - // don't display toolbar for code blocks, code-lines inline code. - if (value.startBlock.type.match(/code/)) return; - - // don't show until user has released pointing device button - if (this.mouseDown) return; - - this.active = true; - this.link = this.link || link; - - const padding = 16; - const selection = window.getSelection(); - let rect; - - if (link) { - rect = findDOMNode(link).getBoundingClientRect(); - } else if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - rect = range.getBoundingClientRect(); - } - - if (!rect || (rect.top === 0 && rect.left === 0)) { - return; - } - - const left = - rect.left + window.scrollX - this.menu.offsetWidth / 2 + rect.width / 2; - this.top = `${Math.round( - rect.top + window.scrollY - this.menu.offsetHeight - )}px`; - this.left = `${Math.round(Math.max(padding, left))}px`; - }; - - setRef = (ref: HTMLElement) => { - this.menu = ref; - }; - - render() { - const style = { - top: this.top, - left: this.left, - }; - - return ( - - - {this.link ? ( - - ) : ( - - )} - - - ); - } -} - -const Menu = styled.div` - padding: 8px 16px; - position: absolute; - z-index: 2; - top: -10000px; - left: -10000px; - opacity: 0; - background-color: #2f3336; - border-radius: 4px; - transform: scale(0.95); - transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275), - transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275); - transition-delay: 250ms; - line-height: 0; - height: 40px; - min-width: 300px; - - ${({ active }) => - active && - ` - transform: translateY(-6px) scale(1); - opacity: 1; - `}; - - @media print { - display: none; - } -`; diff --git a/app/components/Editor/components/Toolbar/components/DocumentResult.js b/app/components/Editor/components/Toolbar/components/DocumentResult.js deleted file mode 100644 index 6f0c83e0..00000000 --- a/app/components/Editor/components/Toolbar/components/DocumentResult.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import React from 'react'; -import styled from 'styled-components'; -import { fontWeight, color } from 'shared/styles/constants'; -import Document from 'models/Document'; -import NextIcon from 'components/Icon/NextIcon'; - -type Props = { - innerRef?: Function, - onClick: SyntheticEvent => void, - document: Document, -}; - -function DocumentResult({ document, ...rest }: Props) { - return ( - - - - - {document.title} - - ); -} - -const ListItem = styled.a` - display: flex; - align-items: center; - height: 28px; - padding: 6px 8px 6px 0; - color: ${color.white}; - font-size: 15px; - overflow: hidden; - white-space: nowrap; - - i { - visibility: hidden; - } - - &:hover, - &:focus, - &:active { - font-weight: ${fontWeight.medium}; - outline: none; - - i { - visibility: visible; - } - } -`; - -export default DocumentResult; diff --git a/app/components/Editor/components/Toolbar/components/FormattingToolbar.js b/app/components/Editor/components/Toolbar/components/FormattingToolbar.js deleted file mode 100644 index c9d9c22a..00000000 --- a/app/components/Editor/components/Toolbar/components/FormattingToolbar.js +++ /dev/null @@ -1,115 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import styled from 'styled-components'; -import { Editor } from 'slate-react'; -import ToolbarButton from './ToolbarButton'; -import BoldIcon from 'components/Icon/BoldIcon'; -import CodeIcon from 'components/Icon/CodeIcon'; -import Heading1Icon from 'components/Icon/Heading1Icon'; -import Heading2Icon from 'components/Icon/Heading2Icon'; -import ItalicIcon from 'components/Icon/ItalicIcon'; -import BlockQuoteIcon from 'components/Icon/BlockQuoteIcon'; -import LinkIcon from 'components/Icon/LinkIcon'; -import StrikethroughIcon from 'components/Icon/StrikethroughIcon'; - -class FormattingToolbar extends Component { - props: { - editor: Editor, - onCreateLink: SyntheticEvent => void, - }; - - /** - * Check if the current selection has a mark with `type` in it. - * - * @param {String} type - * @return {Boolean} - */ - hasMark = (type: string) => { - return this.props.editor.value.marks.some(mark => mark.type === type); - }; - - isBlock = (type: string) => { - const startBlock = this.props.editor.value.startBlock; - return startBlock && startBlock.type === type; - }; - - /** - * When a mark button is clicked, toggle the current mark. - * - * @param {Event} ev - * @param {String} type - */ - onClickMark = (ev: SyntheticEvent, type: string) => { - ev.preventDefault(); - this.props.editor.change(change => change.toggleMark(type)); - }; - - onClickBlock = (ev: SyntheticEvent, type: string) => { - ev.preventDefault(); - this.props.editor.change(change => change.setBlock(type)); - }; - - handleCreateLink = (ev: SyntheticEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - - const data = { href: '' }; - this.props.editor.change(change => { - change.wrapInline({ type: 'link', data }); - this.props.onCreateLink(ev); - }); - }; - - renderMarkButton = (type: string, IconClass: Function) => { - const isActive = this.hasMark(type); - const onMouseDown = ev => this.onClickMark(ev, type); - - return ( - - - - ); - }; - - renderBlockButton = (type: string, IconClass: Function) => { - const isActive = this.isBlock(type); - const onMouseDown = ev => - this.onClickBlock(ev, isActive ? 'paragraph' : type); - - return ( - - - - ); - }; - - render() { - return ( - - {this.renderMarkButton('bold', BoldIcon)} - {this.renderMarkButton('italic', ItalicIcon)} - {this.renderMarkButton('deleted', StrikethroughIcon)} - {this.renderMarkButton('code', CodeIcon)} - - {this.renderBlockButton('heading1', Heading1Icon)} - {this.renderBlockButton('heading2', Heading2Icon)} - {this.renderBlockButton('block-quote', BlockQuoteIcon)} - - - - - - ); - } -} - -const Separator = styled.div` - height: 100%; - width: 1px; - background: #fff; - opacity: 0.2; - display: inline-block; - margin-left: 10px; -`; - -export default FormattingToolbar; diff --git a/app/components/Editor/components/Toolbar/components/LinkToolbar.js b/app/components/Editor/components/Toolbar/components/LinkToolbar.js deleted file mode 100644 index 6026657d..00000000 --- a/app/components/Editor/components/Toolbar/components/LinkToolbar.js +++ /dev/null @@ -1,231 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import { findDOMNode } from 'react-dom'; -import { observable, action } from 'mobx'; -import { observer, inject } from 'mobx-react'; -import { withRouter } from 'react-router-dom'; -import { Node } from 'slate'; -import { Editor } from 'slate-react'; -import styled from 'styled-components'; -import ArrowKeyNavigation from 'boundless-arrow-key-navigation'; -import ToolbarButton from './ToolbarButton'; -import DocumentResult from './DocumentResult'; -import DocumentsStore from 'stores/DocumentsStore'; -import keydown from 'react-keydown'; -import CloseIcon from 'components/Icon/CloseIcon'; -import OpenIcon from 'components/Icon/OpenIcon'; -import TrashIcon from 'components/Icon/TrashIcon'; -import Flex from 'shared/components/Flex'; - -@keydown -@observer -class LinkToolbar extends Component { - wrapper: HTMLSpanElement; - input: HTMLInputElement; - firstDocument: HTMLElement; - - props: { - editor: Editor, - link: Node, - documents: DocumentsStore, - onBlur: () => *, - }; - - originalValue: string = ''; - @observable isEditing: boolean = false; - @observable isFetching: boolean = false; - @observable resultIds: string[] = []; - @observable searchTerm: ?string = null; - - componentDidMount() { - this.originalValue = this.props.link.data.get('href'); - this.isEditing = !!this.originalValue; - - setImmediate(() => - window.addEventListener('click', this.handleOutsideMouseClick) - ); - } - - componentWillUnmount() { - window.removeEventListener('click', this.handleOutsideMouseClick); - } - - handleOutsideMouseClick = (ev: SyntheticMouseEvent) => { - const element = findDOMNode(this.wrapper); - - if ( - !element || - (ev.target instanceof HTMLElement && element.contains(ev.target)) || - (ev.button && ev.button !== 0) - ) { - return; - } - - ev.preventDefault(); - this.save(this.input.value); - }; - - @action - search = async () => { - this.isFetching = true; - - if (this.searchTerm) { - try { - this.resultIds = await this.props.documents.search(this.searchTerm); - } catch (err) { - console.error(err); - } - } else { - this.resultIds = []; - } - - this.isFetching = false; - }; - - selectDocument = (ev, document) => { - ev.preventDefault(); - this.save(document.url); - }; - - onKeyDown = (ev: SyntheticKeyboardEvent & SyntheticInputEvent) => { - switch (ev.keyCode) { - case 13: // enter - ev.preventDefault(); - return this.save(ev.target.value); - case 27: // escape - return this.save(this.originalValue); - case 40: // down - ev.preventDefault(); - if (this.firstDocument) { - const element = findDOMNode(this.firstDocument); - if (element instanceof HTMLElement) element.focus(); - } - break; - default: - } - }; - - onChange = (ev: SyntheticKeyboardEvent & SyntheticInputEvent) => { - try { - new URL(ev.target.value); - } catch (err) { - // this is not a valid url, show search suggestions - this.searchTerm = ev.target.value; - this.search(); - return; - } - this.resultIds = []; - }; - - removeLink = () => { - this.save(''); - }; - - openLink = () => { - const href = this.props.link.data.get('href'); - window.open(href, '_blank'); - }; - - save = (href: string) => { - const { editor, link } = this.props; - href = href.trim(); - - editor.change(change => { - if (href) { - change.setNodeByKey(link.key, { type: 'link', data: { href } }); - } else if (link) { - change.unwrapInlineByKey(link.key); - } - change.deselect(); - this.props.onBlur(); - }); - }; - - setFirstDocumentRef = ref => { - this.firstDocument = ref; - }; - - render() { - const href = this.props.link.data.get('href'); - const hasResults = this.resultIds.length > 0; - - return ( - (this.wrapper = ref)}> - - (this.input = ref)} - defaultValue={href} - placeholder="Search or paste a link…" - onKeyDown={this.onKeyDown} - onChange={this.onChange} - autoFocus={href === ''} - /> - {this.isEditing && ( - - - - )} - - {this.isEditing ? : } - - - {hasResults && ( - - - {this.resultIds.map((id, index) => { - const document = this.props.documents.getById(id); - if (!document) return null; - - return ( - - index === 0 && this.setFirstDocumentRef(ref) - } - document={document} - key={document.id} - onClick={ev => this.selectDocument(ev, document)} - /> - ); - })} - - - )} - - ); - } -} - -const SearchResults = styled.div` - background: #2f3336; - position: absolute; - top: 100%; - width: 100%; - height: auto; - left: 0; - padding: 8px; - margin-top: -3px; - margin-bottom: 0; - border-radius: 0 0 4px 4px; -`; - -const LinkEditor = styled(Flex)` - margin-left: -8px; - margin-right: -8px; -`; - -const Input = styled.input` - font-size: 15px; - background: rgba(255, 255, 255, 0.1); - border-radius: 2px; - padding: 4px 8px; - border: 0; - margin: 0; - outline: none; - color: #fff; - flex-grow: 1; -`; - -export default withRouter(inject('documents')(LinkToolbar)); diff --git a/app/components/Editor/components/Toolbar/components/ToolbarButton.js b/app/components/Editor/components/Toolbar/components/ToolbarButton.js deleted file mode 100644 index 9509934c..00000000 --- a/app/components/Editor/components/Toolbar/components/ToolbarButton.js +++ /dev/null @@ -1,26 +0,0 @@ -// @flow -import styled from 'styled-components'; - -export default styled.button` - display: inline-block; - flex: 0; - width: 24px; - height: 24px; - cursor: pointer; - margin-left: 10px; - border: none; - background: none; - transition: opacity 100ms ease-in-out; - padding: 0; - opacity: 0.7; - - &:first-child { - margin-left: 0; - } - - &:hover { - opacity: 1; - } - - ${({ active }) => active && 'opacity: 1;'}; -`; diff --git a/app/components/Editor/components/Toolbar/index.js b/app/components/Editor/components/Toolbar/index.js deleted file mode 100644 index 8c7e3c39..00000000 --- a/app/components/Editor/components/Toolbar/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import Toolbar from './Toolbar'; -export default Toolbar; diff --git a/app/components/Editor/headingToSlug.js b/app/components/Editor/headingToSlug.js deleted file mode 100644 index 59f3fdbb..00000000 --- a/app/components/Editor/headingToSlug.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow -import { escape } from 'lodash'; -import { Document, Block, Node } from 'slate'; -import slug from 'slug'; - -// finds the index of this heading in the document compared to other headings -// with the same slugified text -function indexOfType(document, heading) { - const slugified = escape(slug(heading.text)); - const headings = document.nodes.filter((node: Block) => { - if (!node.text) return false; - return node.type.match(/^heading/) && slugified === escape(slug(node.text)); - }); - - return headings.indexOf(heading); -} - -// calculates a unique slug for this heading based on it's text and position -// in the document that is as stable as possible -export default function headingToSlug(document: Document, node: Node) { - const slugified = escape(slug(node.text)); - const index = indexOfType(document, node); - if (index === 0) return slugified; - return `${slugified}-${index}`; -} diff --git a/app/components/Editor/index.js b/app/components/Editor/index.js deleted file mode 100644 index 223d4abd..00000000 --- a/app/components/Editor/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import Editor from './Editor'; -export default Editor; diff --git a/app/components/Editor/marks.js b/app/components/Editor/marks.js deleted file mode 100644 index a88b6c27..00000000 --- a/app/components/Editor/marks.js +++ /dev/null @@ -1,27 +0,0 @@ -// @flow -import React from 'react'; -import InlineCode from './components/InlineCode'; -import { Mark } from 'slate'; - -type Props = { - children: React$Element<*>, - mark: Mark, -}; - -export default function renderMark(props: Props) { - switch (props.mark.type) { - case 'bold': - return {props.children}; - case 'code': - return {props.children}; - case 'italic': - return {props.children}; - case 'underlined': - return {props.children}; - case 'deleted': - return {props.children}; - case 'added': - return {props.children}; - default: - } -} diff --git a/app/components/Editor/nodes.js b/app/components/Editor/nodes.js deleted file mode 100644 index fd85fccd..00000000 --- a/app/components/Editor/nodes.js +++ /dev/null @@ -1,77 +0,0 @@ -// @flow -import React from 'react'; -import Code from './components/Code'; -import BlockToolbar from './components/Toolbar/BlockToolbar'; -import HorizontalRule from './components/HorizontalRule'; -import Image from './components/Image'; -import Link from './components/Link'; -import ListItem from './components/ListItem'; -import TodoList from './components/TodoList'; -import { - Heading1, - Heading2, - Heading3, - Heading4, - Heading5, - Heading6, -} from './components/Heading'; -import Paragraph from './components/Paragraph'; -import type { SlateNodeProps } from './types'; - -type Options = { - onInsertImage: *, -}; - -export default function createRenderNode({ onInsertImage }: Options) { - return function renderNode(props: SlateNodeProps) { - const { attributes } = props; - - switch (props.node.type) { - case 'paragraph': - return ; - case 'block-toolbar': - return ; - case 'block-quote': - return
    {props.children}
    ; - case 'bulleted-list': - return
      {props.children}
    ; - case 'ordered-list': - return
      {props.children}
    ; - case 'todo-list': - return {props.children}; - case 'table': - return {props.children}
    ; - case 'table-row': - return {props.children}; - case 'table-head': - return {props.children}; - case 'table-cell': - return {props.children}; - case 'list-item': - return ; - case 'horizontal-rule': - return ; - case 'code': - return ; - case 'code-line': - return
    {props.children}
    ; - case 'image': - return ; - case 'link': - return ; - case 'heading1': - return ; - case 'heading2': - return ; - case 'heading3': - return ; - case 'heading4': - return ; - case 'heading5': - return ; - case 'heading6': - return ; - default: - } - }; -} diff --git a/app/components/Editor/plugins.js b/app/components/Editor/plugins.js deleted file mode 100644 index b50729f6..00000000 --- a/app/components/Editor/plugins.js +++ /dev/null @@ -1,55 +0,0 @@ -// @flow -import InsertImages from '@tommoor/slate-drop-or-paste-images'; -import PasteLinkify from 'slate-paste-linkify'; -import CollapseOnEscape from 'slate-collapse-on-escape'; -import TrailingBlock from 'slate-trailing-block'; -import EditCode from 'slate-edit-code'; -import Prism from 'slate-prism'; -import EditList from './plugins/EditList'; -import KeyboardShortcuts from './plugins/KeyboardShortcuts'; -import MarkdownShortcuts from './plugins/MarkdownShortcuts'; -import { insertImageFile } from './changes'; - -type Options = { - onImageUploadStart: () => void, - onImageUploadStop: () => void, -}; - -const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => { - return [ - PasteLinkify({ - type: 'link', - collapseTo: 'end', - }), - InsertImages({ - extensions: ['png', 'jpg', 'gif', 'webp'], - insertImage: async (change, file, editor) => { - return change.call( - insertImageFile, - file, - editor, - onImageUploadStart, - onImageUploadStop - ); - }, - }), - EditList, - EditCode({ - containerType: 'code', - lineType: 'code-line', - exitBlocktype: 'paragraph', - allowMarks: false, - selectAll: true, - }), - Prism({ - onlyIn: node => node.type === 'code', - getSyntax: node => 'javascript', - }), - CollapseOnEscape({ toEdge: 'end' }), - TrailingBlock({ type: 'paragraph' }), - KeyboardShortcuts(), - MarkdownShortcuts(), - ]; -}; - -export default createPlugins; diff --git a/app/components/Editor/plugins/EditList.js b/app/components/Editor/plugins/EditList.js deleted file mode 100644 index 9d3dea9a..00000000 --- a/app/components/Editor/plugins/EditList.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow -import EditList from 'slate-edit-list'; - -export default EditList({ - types: ['ordered-list', 'bulleted-list', 'todo-list'], - typeItem: 'list-item', - typeDefault: 'paragraph', -}); diff --git a/app/components/Editor/plugins/KeyboardShortcuts.js b/app/components/Editor/plugins/KeyboardShortcuts.js deleted file mode 100644 index 5cc6dd73..00000000 --- a/app/components/Editor/plugins/KeyboardShortcuts.js +++ /dev/null @@ -1,35 +0,0 @@ -// @flow -import { Change } from 'slate'; -import { isModKey } from '../utils'; - -export default function KeyboardShortcuts() { - return { - onKeyDown(ev: SyntheticKeyboardEvent, change: Change) { - if (!isModKey(ev)) return null; - - switch (ev.key) { - case 'b': - return this.toggleMark(change, 'bold'); - case 'i': - return this.toggleMark(change, 'italic'); - case 'u': - return this.toggleMark(change, 'underlined'); - case 'd': - return this.toggleMark(change, 'deleted'); - case 'k': - return change.wrapInline({ type: 'link', data: { href: '' } }); - default: - return null; - } - }, - - toggleMark(change: Change, type: string) { - const { value } = change; - // don't allow formatting of document title - const firstNode = value.document.nodes.first(); - if (firstNode === value.startBlock) return; - - change.toggleMark(type); - }, - }; -} diff --git a/app/components/Editor/plugins/MarkdownShortcuts.js b/app/components/Editor/plugins/MarkdownShortcuts.js deleted file mode 100644 index 912a8a65..00000000 --- a/app/components/Editor/plugins/MarkdownShortcuts.js +++ /dev/null @@ -1,287 +0,0 @@ -// @flow -import { Change } from 'slate'; - -const inlineShortcuts = [ - { mark: 'bold', shortcut: '**' }, - { mark: 'bold', shortcut: '__' }, - { mark: 'italic', shortcut: '*' }, - { mark: 'italic', shortcut: '_' }, - { mark: 'code', shortcut: '`' }, - { mark: 'added', shortcut: '++' }, - { mark: 'deleted', shortcut: '~~' }, -]; - -export default function MarkdownShortcuts() { - return { - onKeyDown(ev: SyntheticKeyboardEvent, change: Change) { - switch (ev.key) { - case '-': - return this.onDash(ev, change); - case '`': - return this.onBacktick(ev, change); - case 'Tab': - return this.onTab(ev, change); - case ' ': - return this.onSpace(ev, change); - case 'Backspace': - return this.onBackspace(ev, change); - case 'Enter': - return this.onEnter(ev, change); - default: - return null; - } - }, - - /** - * On space, if it was after an auto-markdown shortcut, convert the current - * node into the shortcut's corresponding type. - */ - onSpace(ev: SyntheticKeyboardEvent, change: Change) { - const { value } = change; - if (value.isExpanded) return; - const { startBlock, startOffset } = value; - - // no markdown shortcuts work in headings - if (startBlock.type.match(/heading/)) return; - - const chars = startBlock.text.slice(0, startOffset).trim(); - const type = this.getType(chars); - - if (type) { - if (type === 'list-item' && startBlock.type === 'list-item') return; - ev.preventDefault(); - - let checked; - if (chars === '[x]') checked = true; - if (chars === '[ ]') checked = false; - - change - .extendToStartOf(startBlock) - .delete() - .setBlock( - { - type, - data: { checked }, - }, - { normalize: false } - ); - - if (type === 'list-item') { - if (checked !== undefined) { - change.wrapBlock('todo-list'); - } else if (chars === '1.') { - change.wrapBlock('ordered-list'); - } else { - change.wrapBlock('bulleted-list'); - } - } - - return true; - } - - for (const key of inlineShortcuts) { - // find all inline characters - let { mark, shortcut } = key; - let inlineTags = []; - - // only add tags if they have spaces around them or the tag is beginning - // or the end of the block - for (let i = 0; i < startBlock.text.length; i++) { - const { text } = startBlock; - const start = i; - const end = i + shortcut.length; - const beginningOfBlock = start === 0; - const endOfBlock = end === text.length; - const surroundedByWhitespaces = [ - text.slice(start - 1, start), - text.slice(end, end + 1), - ].includes(' '); - - if ( - text.slice(start, end) === shortcut && - (beginningOfBlock || endOfBlock || surroundedByWhitespaces) - ) { - inlineTags.push(i); - } - } - - // if we have multiple tags then mark the text between as inline code - if (inlineTags.length > 1) { - const firstText = startBlock.getFirstText(); - const firstCodeTagIndex = inlineTags[0]; - const lastCodeTagIndex = inlineTags[inlineTags.length - 1]; - return change - .removeTextByKey(firstText.key, lastCodeTagIndex, shortcut.length) - .removeTextByKey(firstText.key, firstCodeTagIndex, shortcut.length) - .moveOffsetsTo( - firstCodeTagIndex, - lastCodeTagIndex - shortcut.length - ) - .addMark(mark) - .collapseToEnd() - .removeMark(mark); - } - } - }, - - onDash(ev: SyntheticKeyboardEvent, change: Change) { - const { value } = change; - if (value.isExpanded) return; - const { startBlock, startOffset } = value; - const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, ''); - - if (chars === '--') { - ev.preventDefault(); - return change - .extendToStartOf(startBlock) - .delete() - .setBlock( - { - type: 'horizontal-rule', - isVoid: true, - }, - { normalize: false } - ) - .insertBlock('paragraph') - .collapseToStart(); - } - }, - - onBacktick(ev: SyntheticKeyboardEvent, change: Change) { - const { value } = change; - if (value.isExpanded) return; - const { startBlock, startOffset } = value; - const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, ''); - - if (chars === '``') { - ev.preventDefault(); - return change - .extendToStartOf(startBlock) - .delete() - .setBlock({ type: 'code' }); - } - }, - - onBackspace(ev: SyntheticKeyboardEvent, change: Change) { - const { value } = change; - if (value.isExpanded) return; - const { startBlock, selection, startOffset } = value; - - // If at the start of a non-paragraph, convert it back into a paragraph - if (startOffset === 0) { - if (startBlock.type === 'paragraph') return; - ev.preventDefault(); - - change.setBlock('paragraph'); - - if (startBlock.type === 'list-item') { - change.unwrapBlock('bulleted-list'); - } - - return change; - } - - // If at the end of a code mark hitting backspace should remove the mark - if (selection.isCollapsed) { - const marksAtCursor = startBlock.getMarksAtRange(selection); - const codeMarksAtCursor = marksAtCursor.filter( - mark => mark.type === 'code' - ); - - if (codeMarksAtCursor.size > 0) { - ev.preventDefault(); - - const textNode = startBlock.getTextAtOffset(startOffset); - const charsInCodeBlock = textNode.characters - .takeUntil((v, k) => k === startOffset) - .reverse() - .takeUntil((v, k) => !v.marks.some(mark => mark.type === 'code')); - - change.removeMarkByKey( - textNode.key, - startOffset - charsInCodeBlock.size, - startOffset, - 'code' - ); - } - } - }, - - /** - * On tab, if at the end of the heading jump to the main body content - * as if it is another input field (act the same as enter). - */ - onTab(ev: SyntheticKeyboardEvent, change: Change) { - const { value } = change; - - if (value.startBlock.type === 'heading1') { - ev.preventDefault(); - change.splitBlock().setBlock('paragraph'); - } - }, - - /** - * On return, if at the end of a node type that should not be extended, - * create a new paragraph below it. - */ - onEnter(ev: SyntheticKeyboardEvent, change: Change) { - const { value } = change; - if (value.isExpanded) return; - - const { startBlock, startOffset, endOffset } = value; - if (startOffset === 0 && startBlock.length === 0) - return this.onBackspace(ev, change); - - // Hitting enter at the end of the line reverts to standard behavior - if (endOffset === startBlock.length) return; - - // Hitting enter while an image is selected should jump caret below and - // insert a new paragraph - if (startBlock.type === 'image') { - ev.preventDefault(); - return change.collapseToEnd().insertBlock('paragraph'); - } - - // Hitting enter in a heading or blockquote will split the node at that - // point and make the new node a paragraph - if ( - startBlock.type.startsWith('heading') || - startBlock.type === 'block-quote' - ) { - ev.preventDefault(); - return change.splitBlock().setBlock('paragraph'); - } - }, - - /** - * Get the block type for a series of auto-markdown shortcut `chars`. - */ - getType(chars: string) { - switch (chars) { - case '*': - case '-': - case '+': - case '1.': - case '[ ]': - case '[x]': - return 'list-item'; - case '>': - return 'block-quote'; - case '#': - return 'heading1'; - case '##': - return 'heading2'; - case '###': - return 'heading3'; - case '####': - return 'heading4'; - case '#####': - return 'heading5'; - case '######': - return 'heading6'; - default: - return null; - } - }, - }; -} diff --git a/app/components/Editor/schema.js b/app/components/Editor/schema.js deleted file mode 100644 index 75a3351b..00000000 --- a/app/components/Editor/schema.js +++ /dev/null @@ -1,75 +0,0 @@ -// @flow -import { Block, Change, Node, Mark } from 'slate'; - -const schema = { - blocks: { - heading1: { nodes: [{ objects: ['text'] }], marks: [''] }, - heading2: { nodes: [{ objects: ['text'] }], marks: [''] }, - heading3: { nodes: [{ objects: ['text'] }], marks: [''] }, - heading4: { nodes: [{ objects: ['text'] }], marks: [''] }, - heading5: { nodes: [{ objects: ['text'] }], marks: [''] }, - heading6: { nodes: [{ objects: ['text'] }], marks: [''] }, - 'block-quote': { marks: [''] }, - table: { - nodes: [{ types: ['table-row', 'table-head', 'table-cell'] }], - }, - 'horizontal-rule': { - isVoid: true, - }, - 'block-toolbar': { - isVoid: true, - }, - }, - document: { - nodes: [ - { types: ['heading1'], min: 1, max: 1 }, - { - types: [ - 'paragraph', - 'heading1', - 'heading2', - 'heading3', - 'heading4', - 'heading5', - 'heading6', - 'block-quote', - 'code', - 'horizontal-rule', - 'image', - 'bulleted-list', - 'ordered-list', - 'todo-list', - 'block-toolbar', - 'table', - ], - min: 1, - }, - ], - normalize: ( - change: Change, - reason: string, - { - node, - child, - mark, - index, - }: { node: Node, mark?: Mark, child: Node, index: number } - ) => { - switch (reason) { - case 'child_type_invalid': { - return change.setNodeByKey( - child.key, - index === 0 ? 'heading1' : 'paragraph' - ); - } - case 'child_required': { - const block = Block.create(index === 0 ? 'heading1' : 'paragraph'); - return change.insertNodeByKey(node.key, index, block); - } - default: - } - }, - }, -}; - -export default schema; diff --git a/app/components/Editor/serializer.js b/app/components/Editor/serializer.js deleted file mode 100644 index 01f20390..00000000 --- a/app/components/Editor/serializer.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import MarkdownSerializer from 'slate-md-serializer'; -export default new MarkdownSerializer(); diff --git a/app/components/Editor/types.js b/app/components/Editor/types.js deleted file mode 100644 index d39ed74e..00000000 --- a/app/components/Editor/types.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow -import { Value, Change, Node } from 'slate'; -import { Editor } from 'slate-react'; - -export type SlateNodeProps = { - children: React$Element<*>, - readOnly: boolean, - attributes: Object, - value: Value, - editor: Editor, - node: Node, - parent: Node, -}; - -export type Plugin = { - validateNode?: Node => *, - onClick?: SyntheticEvent => *, - onKeyDown?: (SyntheticKeyboardEvent, Change) => *, -}; diff --git a/app/components/Editor/utils.js b/app/components/Editor/utils.js deleted file mode 100644 index 1425838a..00000000 --- a/app/components/Editor/utils.js +++ /dev/null @@ -1,11 +0,0 @@ -// @flow - -/** - * Detect Cmd or Ctrl by platform for keyboard shortcuts - */ -export function isModKey(event: SyntheticKeyboardEvent) { - const isMac = - typeof window !== 'undefined' && - /Mac|iPod|iPhone|iPad/.test(window.navigator.platform); - return isMac ? event.metaKey : event.ctrlKey; -} diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index b0ff12d4..65fb24f0 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -7,6 +7,7 @@ import { observer, inject } from 'mobx-react'; import { withRouter, Prompt } from 'react-router-dom'; import type { Location } from 'react-router-dom'; import keydown from 'react-keydown'; +import Editor from 'rich-markdown-editor'; import Flex from 'shared/components/Flex'; import { collectionUrl, @@ -17,6 +18,7 @@ import { matchDocumentMove, } from 'utils/routeHelpers'; + import Document from 'models/Document'; import Actions from './components/Actions'; import DocumentMove from './components/DocumentMove'; @@ -128,8 +130,8 @@ class DocumentScene extends React.Component { }; loadEditor = async () => { - const EditorImport = await import('components/Editor'); - this.editorComponent = EditorImport.default; + // const EditorImport = await import('rich-markdown-editor'); + // this.editorComponent = EditorImport.default; }; get isEditing() { @@ -205,7 +207,7 @@ class DocumentScene extends React.Component { } render() { - const Editor = this.editorComponent; + // const Editor = this.editorComponent; const isMoving = this.props.match.path === matchDocumentMove; const document = this.document; const titleText = diff --git a/package.json b/package.json index 32e05464..6afec0bf 100644 --- a/package.json +++ b/package.json @@ -162,6 +162,7 @@ "react-waypoint": "^7.3.1", "redis": "^2.6.2", "redis-lock": "^0.1.0", + "rich-markdown-editor": "*", "rimraf": "^2.5.4", "safestart": "1.1.0", "sequelize": "4.28.6", diff --git a/yarn.lock b/yarn.lock index 90a51967..b2424798 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8517,6 +8517,10 @@ retry-as-promised@^2.3.1: bluebird "^3.4.6" debug "^2.6.9" +rich-markdown-editor@*: + version "0.0.1" + resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-0.0.1.tgz#e6c22cc8d0bff7304912773372fcc8ab8f44b461" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"