diff --git a/app/components/CenteredContent/CenteredContent.js b/app/components/CenteredContent/CenteredContent.js index bcfaedb8..b3736f6c 100644 --- a/app/components/CenteredContent/CenteredContent.js +++ b/app/components/CenteredContent/CenteredContent.js @@ -8,7 +8,7 @@ type Props = { const Container = styled.div` width: 100%; - margin: 60px; + padding: 60px; `; const Content = styled.div` diff --git a/app/components/Editor/Editor.js b/app/components/Editor/Editor.js index 9de55c50..d767e602 100644 --- a/app/components/Editor/Editor.js +++ b/app/components/Editor/Editor.js @@ -46,7 +46,6 @@ class MarkdownEditor extends Component { this.renderNode = createRenderNode({ onInsertImage: this.insertImageFile, - onChange: this.onChange, }); this.plugins = createPlugins({ onImageUploadStart: props.onImageUploadStart, @@ -76,6 +75,7 @@ class MarkdownEditor extends Component { } onChange = (change: Change) => { + // TODO: Lets avoid constantly serializing to Markdown if (this.editorValue !== change.value) { this.props.onChange(Markdown.serialize(change.value)); this.editorValue = change.value; @@ -99,7 +99,7 @@ class MarkdownEditor extends Component { } }; - insertImageFile = async (file: window.File) => { + insertImageFile = (file: window.File) => { this.editor.change(change => change.call( insertImageFile, diff --git a/app/components/Editor/changes.js b/app/components/Editor/changes.js index d4e35ec7..7a182994 100644 --- a/app/components/Editor/changes.js +++ b/app/components/Editor/changes.js @@ -18,17 +18,17 @@ export function splitAndInsertBlock(change: Change, options: Options) { // lists get some special treatment if (parent && parent.type === 'list-item') { - change = changes.unwrapList( - changes - .splitListItem(change.collapseToStart()) - .collapseToEndOfPreviousBlock() - ); + change + .collapseToStart() + .call(changes.splitListItem) + .collapseToEndOfPreviousBlock() + .call(changes.unwrapList); } - change = change.insertBlock(type); + change.insertBlock(type); - if (wrapper) change = change.wrapBlock(wrapper); - if (append) change = change.insertBlock(append); + if (wrapper) change.wrapBlock(wrapper); + if (append) change.insertBlock(append); return change; } diff --git a/app/components/Editor/components/BlockInsert.js b/app/components/Editor/components/BlockInsert.js index 8eab208f..372a196d 100644 --- a/app/components/Editor/components/BlockInsert.js +++ b/app/components/Editor/components/BlockInsert.js @@ -84,11 +84,13 @@ export default class BlockInsert extends Component { }; handleClick = (ev: SyntheticMouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + this.mouseMovementSinceClick = 0; this.active = false; const { editor } = this.props; - const type = { type: 'block-toolbar', isVoid: true }; editor.change(change => { // remove any existing toolbars in the document as a fail safe @@ -101,7 +103,7 @@ export default class BlockInsert extends Component { change .collapseToStartOf(this.closestRootNode) .collapseToEndOfPreviousBlock() - .insertBlock(type); + .insertBlock({ type: 'block-toolbar', isVoid: true }); }); }; @@ -133,7 +135,11 @@ const Trigger = styled.div` cursor: pointer; &:hover { - background-color: ${color.smokeDark}; + background-color: ${color.slate}; + + svg { + fill: ${color.white}; + } } ${({ active }) => diff --git a/app/components/Editor/components/Image.js b/app/components/Editor/components/Image.js index c8f7cbab..50784ab4 100644 --- a/app/components/Editor/components/Image.js +++ b/app/components/Editor/components/Image.js @@ -13,11 +13,8 @@ class Image extends Component { const { editor, node } = this.props; const data = node.data.toObject(); - editor.onChange( - editor - .getState() - .change() - .setNodeByKey(node.key, { data: { ...data, alt } }) + editor.change(change => + change.setNodeByKey(node.key, { data: { ...data, alt } }) ); }; diff --git a/app/components/Editor/components/ListItem.js b/app/components/Editor/components/ListItem.js index 7941f97e..abb1283c 100644 --- a/app/components/Editor/components/ListItem.js +++ b/app/components/Editor/components/ListItem.js @@ -10,15 +10,11 @@ export default function ListItem({ ...props }: SlateNodeProps) { const checked = node.data.get('checked'); + console.log('ListItem.checked', checked); if (checked !== undefined) { return ( - + {children} ); diff --git a/app/components/Editor/components/TodoItem.js b/app/components/Editor/components/TodoItem.js index e6020228..f2e5b77e 100644 --- a/app/components/Editor/components/TodoItem.js +++ b/app/components/Editor/components/TodoItem.js @@ -5,7 +5,7 @@ import { color } from 'shared/styles/constants'; import type { SlateNodeProps } from '../types'; export default class TodoItem extends Component { - props: SlateNodeProps & { checked: boolean }; + props: SlateNodeProps; handleChange = (ev: SyntheticInputEvent) => { const checked = ev.target.checked; @@ -16,7 +16,8 @@ export default class TodoItem extends Component { }; render() { - const { children, checked, attributes, readOnly } = this.props; + const { children, node, attributes, readOnly } = this.props; + const checked = node.data.get('checked'); return ( diff --git a/app/components/Editor/components/Toolbar/BlockToolbar.js b/app/components/Editor/components/Toolbar/BlockToolbar.js index ded10b94..cd94d9db 100644 --- a/app/components/Editor/components/Toolbar/BlockToolbar.js +++ b/app/components/Editor/components/Toolbar/BlockToolbar.js @@ -1,5 +1,6 @@ // @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'; @@ -30,21 +31,30 @@ type Options = { class BlockToolbar extends Component { props: Props; + bar: HTMLDivElement; file: HTMLInputElement; - componentWillReceiveProps(nextProps: Props) { - const { editor } = this.props; - const wasActive = editor.value.selection.hasEdgeIn(this.props.node); - const isActive = nextProps.editor.value.selection.hasEdgeIn(nextProps.node); - const becameInactive = !isActive && wasActive; - - if (becameInactive) { - nextProps.editor.change(change => - change.removeNodeByKey(nextProps.node.key) - ); - } + 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(); @@ -59,7 +69,7 @@ class BlockToolbar extends Component { const { editor } = this.props; editor.change(change => { - splitAndInsertBlock(change, options); + change.call(splitAndInsertBlock, options); editor.value.document.nodes.forEach(node => { if (node.type === 'block-toolbar') { @@ -130,7 +140,7 @@ class BlockToolbar extends Component { editor.value.isFocused && editor.value.selection.hasEdgeIn(node); return ( - + (this.bar = ref)}> (this.file = ref)} diff --git a/app/components/Editor/components/Toolbar/Toolbar.js b/app/components/Editor/components/Toolbar/Toolbar.js index 75d97847..1032abfa 100644 --- a/app/components/Editor/components/Toolbar/Toolbar.js +++ b/app/components/Editor/components/Toolbar/Toolbar.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { observable } from 'mobx'; import { observer } from 'mobx-react'; import { Portal } from 'react-portal'; -import { Editor } from 'slate-react'; +import { Editor, findDOMNode } from 'slate-react'; import { Value } from 'slate'; import styled from 'styled-components'; import _ from 'lodash'; @@ -45,14 +45,14 @@ export default class Toolbar extends Component { const { value } = this.props; try { - const selectedLinks = value.startBlock + const selectedLinks = value.document .getInlinesAtRange(value.selection) .filter(node => node.type === 'link'); if (selectedLinks.size) { return selectedLinks.first(); } } catch (err) { - // + // It's okay. } } @@ -74,8 +74,8 @@ export default class Toolbar extends Component { const firstNode = value.document.nodes.first(); if (firstNode === value.startBlock) return; - // don't display toolbar for code blocks - if (value.startBlock.type === 'code') return; + // don't display toolbar for code blocks, code-lines inline code. + if (value.startBlock.type.match(/code/)) return; this.active = true; this.focused = !!link; @@ -84,7 +84,9 @@ export default class Toolbar extends Component { const padding = 16; const selection = window.getSelection(); const range = selection.getRangeAt(0); - const rect = range.getBoundingClientRect(); + const rect = link + ? findDOMNode(link).getBoundingClientRect() + : range.getBoundingClientRect(); if (rect.top === 0 && rect.left === 0) { return; diff --git a/app/components/Editor/components/Toolbar/components/FormattingToolbar.js b/app/components/Editor/components/Toolbar/components/FormattingToolbar.js index 5dd7e6c3..5c988fbf 100644 --- a/app/components/Editor/components/Toolbar/components/FormattingToolbar.js +++ b/app/components/Editor/components/Toolbar/components/FormattingToolbar.js @@ -14,7 +14,7 @@ import StrikethroughIcon from 'components/Icon/StrikethroughIcon'; class FormattingToolbar extends Component { props: { editor: Editor, - onCreateLink: Function, + onCreateLink: () => void, }; /** @@ -52,10 +52,10 @@ class FormattingToolbar extends Component { ev.stopPropagation(); const data = { href: '' }; - this.props.editor.change(change => { - change.wrapInline({ type: 'link', data }); - this.props.onCreateLink(); - }); + this.props.editor.change(change => + change.wrapInline({ type: 'link', data }) + ); + this.props.onCreateLink(); }; renderMarkButton = (type: string, IconClass: Function) => { diff --git a/app/components/Editor/plugins.js b/app/components/Editor/plugins.js index 46e59d89..e8dc46aa 100644 --- a/app/components/Editor/plugins.js +++ b/app/components/Editor/plugins.js @@ -10,13 +10,13 @@ import KeyboardShortcuts from './plugins/KeyboardShortcuts'; import MarkdownShortcuts from './plugins/MarkdownShortcuts'; import { insertImageFile } from './changes'; -const onlyInCode = node => node.type === 'code'; - type Options = { - onImageUploadStart: Function, - onImageUploadStop: Function, + onImageUploadStart: () => void, + onImageUploadStop: () => void, }; +const onlyInCode = node => node.type === 'code'; + const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => { return [ PasteLinkify({ diff --git a/app/components/Editor/plugins/MarkdownShortcuts.js b/app/components/Editor/plugins/MarkdownShortcuts.js index 8d4e7b66..a6862da1 100644 --- a/app/components/Editor/plugins/MarkdownShortcuts.js +++ b/app/components/Editor/plugins/MarkdownShortcuts.js @@ -50,7 +50,17 @@ export default function MarkdownShortcuts() { let checked; if (chars === '[x]') checked = true; if (chars === '[ ]') checked = false; - change.setBlock({ type, data: { checked } }); + + change + .extendToStartOf(startBlock) + .delete() + .setBlock( + { + type, + data: { checked }, + }, + { normalize: false } + ); if (type === 'list-item') { if (checked !== undefined) { @@ -62,7 +72,7 @@ export default function MarkdownShortcuts() { } } - return change.extendToStartOf(startBlock).delete(); + return true; } for (const key of inlineShortcuts) { @@ -70,7 +80,8 @@ export default function MarkdownShortcuts() { 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 + // 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; @@ -85,8 +96,9 @@ export default function MarkdownShortcuts() { 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 @@ -94,7 +106,7 @@ export default function MarkdownShortcuts() { const firstText = startBlock.getFirstText(); const firstCodeTagIndex = inlineTags[0]; const lastCodeTagIndex = inlineTags[inlineTags.length - 1]; - change + return change .removeTextByKey(firstText.key, lastCodeTagIndex, shortcut.length) .removeTextByKey(firstText.key, firstCodeTagIndex, shortcut.length) .moveOffsetsTo( @@ -139,9 +151,7 @@ export default function MarkdownShortcuts() { return change .extendToStartOf(startBlock) .delete() - .setBlock({ - type: 'code', - }); + .setBlock({ type: 'code' }); } }, diff --git a/app/components/Editor/schema.js b/app/components/Editor/schema.js index 14ae2baf..a8331e3d 100644 --- a/app/components/Editor/schema.js +++ b/app/components/Editor/schema.js @@ -2,32 +2,26 @@ import { Block, Change, Node, Mark } from 'slate'; const schema = { - blocks: { - heading1: { marks: [''] }, - heading2: { marks: [''] }, - heading3: { marks: [''] }, - heading4: { marks: [''] }, - heading5: { marks: [''] }, - heading6: { marks: [''] }, - 'ordered-list': { - nodes: [{ types: ['list-item'] }], - }, - 'bulleted-list': { - nodes: [{ types: ['list-item'] }], - }, - table: { - nodes: [{ types: ['table-row', 'table-head', 'table-cell'] }], - }, - image: { - isVoid: true, - }, - 'horizontal-rule': { - isVoid: true, - }, - 'block-toolbar': { - isVoid: true, - }, - }, + // blocks: { + // heading1: { marks: [''] }, + // heading2: { marks: [''] }, + // heading3: { marks: [''] }, + // heading4: { marks: [''] }, + // heading5: { marks: [''] }, + // heading6: { marks: [''] }, + // table: { + // nodes: [{ types: ['table-row', 'table-head', 'table-cell'] }], + // }, + // image: { + // isVoid: true, + // }, + // 'horizontal-rule': { + // isVoid: true, + // }, + // 'block-toolbar': { + // isVoid: true, + // }, + // }, document: { nodes: [ { types: ['heading1'], min: 1, max: 1 }, diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 7aa08b9c..f116a909 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -292,7 +292,6 @@ class DocumentScene extends Component { const Container = styled(Flex)` position: relative; - width: 100%; `; const LoadingState = styled(LoadingPlaceholder)`