From 5f4b5f6d33f6361977f1aacb1e681571040b9e7a Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 15 Oct 2017 19:21:47 -0700 Subject: [PATCH 01/19] Functional TOC --- frontend/components/Editor/Editor.js | 28 +++++----- .../components/Editor/components/Heading.js | 46 +++++++++------- .../components/Editor/components/Minimap.js | 52 +++++++++++++++++++ frontend/components/Editor/headingToSlug.js | 8 +++ frontend/components/Editor/types.js | 1 + 5 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 frontend/components/Editor/components/Minimap.js create mode 100644 frontend/components/Editor/headingToSlug.js diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 922dc5f04..7c4d27a32 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -1,5 +1,6 @@ // @flow import React, { Component } from 'react'; +import { observable } from 'mobx'; import { observer } from 'mobx-react'; import { Editor, Plain } from 'slate'; import keydown from 'react-keydown'; @@ -9,6 +10,7 @@ import Flex from 'components/Flex'; import ClickablePadding from './components/ClickablePadding'; import Toolbar from './components/Toolbar'; import Placeholder from './components/Placeholder'; +import Minimap from './components/Minimap'; import Markdown from './serializer'; import createSchema from './schema'; import createPlugins from './plugins'; @@ -36,10 +38,7 @@ type KeyData = { editor: EditorType; schema: Object; plugins: Array; - - state: { - state: State, - }; + @observable editorState: State; constructor(props: Props) { super(props); @@ -51,9 +50,9 @@ type KeyData = { }); if (props.text) { - this.state = { state: Markdown.deserialize(props.text) }; + this.editorState = Markdown.deserialize(props.text); } else { - this.state = { state: Plain.deserialize('') }; + this.editorState = Plain.deserialize(''); } } @@ -73,12 +72,12 @@ type KeyData = { } } - onChange = (state: State) => { - this.setState({ state }); + onChange = (editorState: State) => { + this.editorState = editorState; }; - onDocumentChange = (document: Document, state: State) => { - this.props.onChange(Markdown.serialize(state)); + onDocumentChange = (document: Document, editorState: State) => { + this.props.onChange(Markdown.serialize(editorState)); }; handleDrop = async (ev: SyntheticEvent) => { @@ -161,7 +160,7 @@ type KeyData = { const transform = state.transform(); transform.collapseToStartOf(state.document); transform.focus(); - this.setState({ state: transform.apply() }); + this.editorState = transform.apply(); }; focusAtEnd = () => { @@ -169,7 +168,7 @@ type KeyData = { const transform = state.transform(); transform.collapseToEndOf(state.document); transform.focus(); - this.setState({ state: transform.apply() }); + this.editorState = transform.apply(); }; render = () => { @@ -184,7 +183,8 @@ type KeyData = { >
- + + (this.editor = ref)} placeholder="Start with a title…" @@ -192,7 +192,7 @@ type KeyData = { schema={this.schema} plugins={this.plugins} emoji={this.props.emoji} - state={this.state.state} + state={this.editorState} onKeyDown={this.onKeyDown} onChange={this.onChange} onDocumentChange={this.onDocumentChange} diff --git a/frontend/components/Editor/components/Heading.js b/frontend/components/Editor/components/Heading.js index c8ceb4464..0a9891d93 100644 --- a/frontend/components/Editor/components/Heading.js +++ b/frontend/components/Editor/components/Heading.js @@ -2,13 +2,12 @@ import React from 'react'; import { Document } from 'slate'; import styled from 'styled-components'; -import _ from 'lodash'; -import slug from 'slug'; +import headingToSlug from '../headingToSlug'; import type { Node, Editor } from '../types'; import Placeholder from './Placeholder'; type Props = { - children: React$Element, + children: React$Element<*>, placeholder?: boolean, parent: Node, node: Node, @@ -31,7 +30,7 @@ function Heading(props: Props) { const parentIsDocument = parent instanceof Document; const firstHeading = parentIsDocument && parent.nodes.first() === node; const showPlaceholder = placeholder && firstHeading && !node.text; - const slugish = _.escape(`${component}-${slug(node.text)}`); + const slugish = headingToSlug(node.type, node.text); const showHash = readOnly && !!slugish; const Component = component; const emoji = editor.props.emoji || ''; @@ -40,8 +39,10 @@ function Heading(props: Props) { emoji && title.match(new RegExp(`^${emoji}\\s`)); return ( - - {children} + + + {children} + {showPlaceholder && {editor.props.placeholder} @@ -53,7 +54,7 @@ function Heading(props: Props) { const Wrapper = styled.div` display: inline; - margin-left: ${props => (props.hasEmoji ? '-1.2em' : 0)} + margin-left: ${(props: Props) => (props.hasEmoji ? '-1.2em' : 0)} `; const Anchor = styled.a` @@ -66,19 +67,28 @@ const Anchor = styled.a` } `; -export const Heading1 = styled(Heading)` +export const StyledHeading = styled(Heading)` position: relative; &:hover { - ${Anchor} { - visibility: visible; - } + ${Anchor} { visibility: visible; } } `; -export const Heading2 = Heading1.withComponent('h2'); -export const Heading3 = Heading1.withComponent('h3'); -export const Heading4 = Heading1.withComponent('h4'); -export const Heading5 = Heading1.withComponent('h5'); -export const Heading6 = Heading1.withComponent('h6'); - -export default Heading; +export const Heading1 = (props: Props) => ( + +); +export const Heading2 = (props: Props) => ( + +); +export const Heading3 = (props: Props) => ( + +); +export const Heading4 = (props: Props) => ( + +); +export const Heading5 = (props: Props) => ( + +); +export const Heading6 = (props: Props) => ( + +); diff --git a/frontend/components/Editor/components/Minimap.js b/frontend/components/Editor/components/Minimap.js new file mode 100644 index 000000000..3d4e096e4 --- /dev/null +++ b/frontend/components/Editor/components/Minimap.js @@ -0,0 +1,52 @@ +// @flow +import React, { Component } from 'react'; +import { List } from 'immutable'; +import headingToSlug from '../headingToSlug'; +import type { State, Block } from '../types'; +import styled from 'styled-components'; + +type Props = { + state: State, +}; + +class Minimap extends Component { + props: Props; + + get headings(): List { + const { state } = this.props; + + return state.document.nodes.filter((node: Block) => { + if (!node.text) return false; + return node.type.match(/^heading/); + }); + } + + render() { + return ( + + + {this.headings.map(heading => ( +
  • + + {heading.text} + +
  • + ))} +
    +
    + ); + } +} + +const Headings = styled.ol` + margin: 0; + padding: 0; +`; + +const Wrapper = styled.div` + position: fixed; + left: 0; + top: 50%; +`; + +export default Minimap; diff --git a/frontend/components/Editor/headingToSlug.js b/frontend/components/Editor/headingToSlug.js new file mode 100644 index 000000000..1eee75dcd --- /dev/null +++ b/frontend/components/Editor/headingToSlug.js @@ -0,0 +1,8 @@ +// @flow +import { escape } from 'lodash'; +import slug from 'slug'; + +export default function headingToSlug(heading: string, title: string) { + const level = heading.replace('heading', 'h'); + return escape(`${level}-${slug(title)}`); +} diff --git a/frontend/components/Editor/types.js b/frontend/components/Editor/types.js index 0db4d68ae..4406f20f9 100644 --- a/frontend/components/Editor/types.js +++ b/frontend/components/Editor/types.js @@ -66,6 +66,7 @@ export type Editor = { export type Node = { key: string, kind: string, + type: string, length: number, text: string, data: Map, From 3d446347f261747b3785864e67a1fcce5b254129 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 15 Oct 2017 22:26:23 -0700 Subject: [PATCH 02/19] TOC now has active heading highlighted --- .../components/Editor/components/Minimap.js | 91 ++++++++++++++++--- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/frontend/components/Editor/components/Minimap.js b/frontend/components/Editor/components/Minimap.js index 3d4e096e4..9178468d6 100644 --- a/frontend/components/Editor/components/Minimap.js +++ b/frontend/components/Editor/components/Minimap.js @@ -1,6 +1,9 @@ // @flow import React, { Component } from 'react'; +import { observable } from 'mobx'; +import { observer } from 'mobx-react'; import { List } from 'immutable'; +import { color } from 'styles/constants'; import headingToSlug from '../headingToSlug'; import type { State, Block } from '../types'; import styled from 'styled-components'; @@ -9,44 +12,104 @@ type Props = { state: State, }; -class Minimap extends Component { +@observer class Minimap extends Component { props: Props; + @observable activeHeading: ?string; + + componentDidMount() { + window.addEventListener('scroll', this.updateActiveHeading); + this.updateActiveHeading(); + } + + componentWillUnmount() { + window.removeEventListener('scroll', this.updateActiveHeading); + } + + updateActiveHeading = () => { + let activeHeading = this.headingElements[0].id; + + for (const element of this.headingElements) { + const bounds = element.getBoundingClientRect(); + if (bounds.top <= 0) activeHeading = element.id; + } + + this.activeHeading = activeHeading; + }; + + get headingElements(): HTMLElement[] { + const elements = []; + const tagNames = ['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 { state } = this.props; return state.document.nodes.filter((node: Block) => { if (!node.text) return false; + if (node.type === 'heading1') return false; return node.type.match(/^heading/); }); } render() { + // 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 => ( -
  • - - {heading.text} - -
  • - ))} -
    + + {this.headings.map(heading => { + const slug = headingToSlug(heading.type, heading.text); + + return ( + + + {heading.text} + + + ); + })} +
    ); } } -const Headings = styled.ol` - margin: 0; +const Anchor = styled.a` + color: ${props => (props.active ? color.primary : color.slate)}; + font-weight: ${props => (props.active ? 500 : 400)}; +`; + +const Sections = styled.ol` + margin: 0 0 0 -8px; padding: 0; + list-style: none; + font-size: 13px; + border-right: 1px solid ${color.slate}; +`; + +const ListItem = styled.li` + position: relative; + margin-left: ${props => (props.type === 'heading2' ? '8px' : '16px')}; + text-align: right; + color: ${color.slate}; + padding-right: 10px; `; const Wrapper = styled.div` position: fixed; - left: 0; - top: 50%; + right: 0; + top: 160px; + padding-right: 20px; + background: ${color.white}; `; export default Minimap; From 1adc983a120648760dab4a9fc217b6c759786772 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 16 Oct 2017 19:30:17 -0700 Subject: [PATCH 03/19] Styling --- .../components/Editor/components/Minimap.js | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/components/Editor/components/Minimap.js b/frontend/components/Editor/components/Minimap.js index 9178468d6..12adb4f49 100644 --- a/frontend/components/Editor/components/Minimap.js +++ b/frontend/components/Editor/components/Minimap.js @@ -88,20 +88,27 @@ const Anchor = styled.a` font-weight: ${props => (props.active ? 500 : 400)}; `; -const Sections = styled.ol` - margin: 0 0 0 -8px; - padding: 0; - list-style: none; - font-size: 13px; - border-right: 1px solid ${color.slate}; -`; - const ListItem = styled.li` position: relative; margin-left: ${props => (props.type === 'heading2' ? '8px' : '16px')}; text-align: right; color: ${color.slate}; padding-right: 10px; + opacity: 0; +`; + +const Sections = styled.ol` + margin: 0 0 0 -8px; + padding: 0; + list-style: none; + font-size: 13px; + border-right: 1px solid ${color.slate}; + + &:hover { + ${ListItem} { + opacity: 1; + } + } `; const Wrapper = styled.div` @@ -110,6 +117,7 @@ const Wrapper = styled.div` top: 160px; padding-right: 20px; background: ${color.white}; + z-index: 100; `; export default Minimap; From aa5b3baf27fe62fc95d2b0751c0bffafd3b78fe2 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 16 Oct 2017 22:05:55 -0700 Subject: [PATCH 04/19] Subtle --- frontend/components/Editor/Editor.js | 4 +-- .../components/{Minimap.js => Contents.js} | 36 +++++++++++++------ 2 files changed, 27 insertions(+), 13 deletions(-) rename frontend/components/Editor/components/{Minimap.js => Contents.js} (77%) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 7c4d27a32..4a016763b 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -10,7 +10,7 @@ import Flex from 'components/Flex'; import ClickablePadding from './components/ClickablePadding'; import Toolbar from './components/Toolbar'; import Placeholder from './components/Placeholder'; -import Minimap from './components/Minimap'; +import Contents from './components/Contents'; import Markdown from './serializer'; import createSchema from './schema'; import createPlugins from './plugins'; @@ -184,7 +184,7 @@ type KeyData = {
    - + (this.editor = ref)} placeholder="Start with a title…" diff --git a/frontend/components/Editor/components/Minimap.js b/frontend/components/Editor/components/Contents.js similarity index 77% rename from frontend/components/Editor/components/Minimap.js rename to frontend/components/Editor/components/Contents.js index 12adb4f49..31382cbc7 100644 --- a/frontend/components/Editor/components/Minimap.js +++ b/frontend/components/Editor/components/Contents.js @@ -12,7 +12,7 @@ type Props = { state: State, }; -@observer class Minimap extends Component { +@observer class Contents extends Component { props: Props; @observable activeHeading: ?string; @@ -68,10 +68,11 @@ type Props = { {this.headings.map(heading => { const slug = headingToSlug(heading.type, heading.text); + const active = this.activeHeading === slug; return ( - - + + {heading.text} @@ -84,8 +85,16 @@ type Props = { } const Anchor = styled.a` - color: ${props => (props.active ? color.primary : color.slate)}; + 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; + + &:hover { + color: ${color.primary}; + } `; const ListItem = styled.li` @@ -93,8 +102,14 @@ const ListItem = styled.li` margin-left: ${props => (props.type === 'heading2' ? '8px' : '16px')}; text-align: right; color: ${color.slate}; - padding-right: 10px; - opacity: 0; + padding-right: 16px; + + &:after { + color: ${props => (props.active ? color.slateDark : color.slate)}; + content: "${props => (props.type === 'heading2' ? '—' : '–')}"; + position: absolute; + right: 0; + } `; const Sections = styled.ol` @@ -102,11 +117,12 @@ const Sections = styled.ol` padding: 0; list-style: none; font-size: 13px; - border-right: 1px solid ${color.slate}; &:hover { - ${ListItem} { + ${Anchor} { opacity: 1; + margin-right: 0; + background: ${color.white}; } } `; @@ -115,9 +131,7 @@ const Wrapper = styled.div` position: fixed; right: 0; top: 160px; - padding-right: 20px; - background: ${color.white}; z-index: 100; `; -export default Minimap; +export default Contents; From 3652b19e99a3fdd114dd26c94a9552c47ad3f8e0 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Sat, 21 Oct 2017 17:16:07 -0700 Subject: [PATCH 05/19] Only use inline tags if they have space around them --- .../components/Editor/plugins/MarkdownShortcuts.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/components/Editor/plugins/MarkdownShortcuts.js b/frontend/components/Editor/plugins/MarkdownShortcuts.js index de530b006..2431b973e 100644 --- a/frontend/components/Editor/plugins/MarkdownShortcuts.js +++ b/frontend/components/Editor/plugins/MarkdownShortcuts.js @@ -73,8 +73,19 @@ 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 for (let i = 0; i < startBlock.text.length; i++) { - if (startBlock.text.slice(i, i + shortcut.length) === shortcut) + const { text } = startBlock; + const start = i; + const end = i + shortcut.length; + if ( + text.slice(start, end) === shortcut && + (start === 0 || + end === text.length || + [text.slice(start - 1, start), text.slice(end, end + 1)].includes( + ' ' + )) + ) inlineTags.push(i); } From 82fdb2a8bc08b1672115d550c25a76cd921afa75 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 21 Oct 2017 20:30:28 -0700 Subject: [PATCH 06/19] Improve code highlighting --- frontend/components/Editor/components/Code.js | 8 +- frontend/index.js | 3 +- frontend/styles/hljs-github-gist.css | 71 ----------- frontend/styles/prism-tomorrow.css | 120 ------------------ frontend/styles/prism.css | 118 +++++++++++++++++ 5 files changed, 124 insertions(+), 196 deletions(-) delete mode 100644 frontend/styles/hljs-github-gist.css delete mode 100644 frontend/styles/prism-tomorrow.css create mode 100644 frontend/styles/prism.css diff --git a/frontend/components/Editor/components/Code.js b/frontend/components/Editor/components/Code.js index d6e2d5385..f370392d0 100644 --- a/frontend/components/Editor/components/Code.js +++ b/frontend/components/Editor/components/Code.js @@ -6,11 +6,13 @@ import { color } from 'styles/constants'; import type { Props } from '../types'; export default function Code({ children, node, readOnly, attributes }: Props) { + const language = node.data.get('language') || 'javascript'; + return ( {readOnly && } -
    -        
    +      
    +        
               {children}
             
           
    @@ -20,7 +22,7 @@ export default function Code({ children, node, readOnly, attributes }: Props) { const Pre = styled.pre` padding: .5em 1em; - background: ${color.smoke}; + background: ${color.smokeLight}; border-radius: 4px; border: 1px solid ${color.smokeDark}; diff --git a/frontend/index.js b/frontend/index.js index 12e49fe7d..3c5d593ae 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -19,8 +19,7 @@ import 'normalize.css/normalize.css'; import 'styles/base.css'; import 'styles/fonts.css'; import 'styles/transitions.css'; -import 'styles/prism-tomorrow.css'; -import 'styles/hljs-github-gist.css'; +import 'styles/prism.css'; import Home from 'scenes/Home'; import Dashboard from 'scenes/Dashboard'; diff --git a/frontend/styles/hljs-github-gist.css b/frontend/styles/hljs-github-gist.css deleted file mode 100644 index 488daf1b8..000000000 --- a/frontend/styles/hljs-github-gist.css +++ /dev/null @@ -1,71 +0,0 @@ -/** - * GitHub Gist Theme - * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro - */ - -.hljs { - display: block; - background: white; - padding: 0.5em; - color: #333333; - overflow-x: auto; -} - -.hljs-comment, -.hljs-meta { - color: #969896; -} - -.hljs-string, -.hljs-variable, -.hljs-template-variable, -.hljs-strong, -.hljs-emphasis, -.hljs-quote { - color: #df5000; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-type { - color: #a71d5d; -} - -.hljs-literal, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute { - color: #0086b3; -} - -.hljs-section, -.hljs-name { - color: #63a35c; -} - -.hljs-tag { - color: #333333; -} - -.hljs-title, -.hljs-attr, -.hljs-selector-id, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #795da3; -} - -.hljs-addition { - color: #55a532; - background-color: #eaffea; -} - -.hljs-deletion { - color: #bd2c00; - background-color: #ffecec; -} - -.hljs-link { - text-decoration: underline; -} diff --git a/frontend/styles/prism-tomorrow.css b/frontend/styles/prism-tomorrow.css deleted file mode 100644 index 5a67be559..000000000 --- a/frontend/styles/prism-tomorrow.css +++ /dev/null @@ -1,120 +0,0 @@ -/** - * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML - * Based on https://github.com/chriskempson/tomorrow-theme - * @author Rose Pritchard - */ - -code[class*='language-'], -pre[class*='language-'] { - color: #ccc; - background: none; - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -/* Code blocks */ -pre[class*='language-'] { - padding: 1em; - margin: 0.5em 0; - overflow: auto; -} - -:not(pre) > code[class*='language-'], -pre[class*='language-'] { - background: #2d2d2d; -} - -/* Inline code */ -:not(pre) > code[class*='language-'] { - padding: 0.1em; - border-radius: 0.3em; - white-space: normal; -} - -.token.comment, -.token.block-comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: #999; -} - -.token.punctuation { - color: #ccc; -} - -.token.tag, -.token.attr-name, -.token.namespace, -.token.deleted { - color: #e2777a; -} - -.token.function-name { - color: #6196cc; -} - -.token.boolean, -.token.number, -.token.function { - color: #f08d49; -} - -.token.property, -.token.class-name, -.token.constant, -.token.symbol { - color: #f8c555; -} - -.token.selector, -.token.important, -.token.atrule, -.token.keyword, -.token.builtin { - color: #cc99cd; -} - -.token.string, -.token.char, -.token.attr-value, -.token.regex, -.token.variable { - color: #7ec699; -} - -.token.operator, -.token.entity, -.token.url { - color: #67cdcc; -} - -.token.important, -.token.bold { - font-weight: bold; -} -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.token.inserted { - color: green; -} diff --git a/frontend/styles/prism.css b/frontend/styles/prism.css new file mode 100644 index 000000000..3655ef339 --- /dev/null +++ b/frontend/styles/prism.css @@ -0,0 +1,118 @@ +/** + * GHColors theme by Avi Aryan (http://aviaryan.in) + * Inspired by Github syntax coloring + */ + +code[class*="language-"], +pre[class*="language-"] { + color: #393A34; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + font-size: 13px; + line-height: 1.4em; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + background: #b3d4fc; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border: 1px solid #dddddd; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .2em; + padding-top: 1px; padding-bottom: 1px; + background: #f8f8f8; + border: 1px solid #dddddd; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #999988; font-style: italic; +} + +.token.namespace { + opacity: .7; +} + +.token.string, +.token.attr-value { + color: #e3116c; +} +.token.punctuation, +.token.operator { + color: #393A34; /* no highlight */ +} + +.token.entity, +.token.url, +.token.symbol, +.token.number, +.token.boolean, +.token.variable, +.token.constant, +.token.property, +.token.regex, +.token.inserted { + color: #36acaa; +} + +.token.atrule, +.token.keyword, +.token.attr-name, +.language-autohotkey .token.selector { + color: #00a4db; +} + +.token.function, +.token.deleted, +.language-autohotkey .token.tag { + color: #9a050f; +} + +.token.tag, +.token.selector, +.language-autohotkey .token.keyword { + color: #00009f; +} + +.token.important, +.token.function, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} From ca46ca2f06cc86ae3d22cce8815b92a53218445f Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Sun, 22 Oct 2017 11:02:05 -0700 Subject: [PATCH 07/19] Added predictive document title --- frontend/scenes/Document/Document.js | 10 ++++++++-- frontend/stores/CollectionsStore.js | 14 ++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 371765cd9..d290ad791 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -22,6 +22,7 @@ import Document from 'models/Document'; import DocumentMove from './components/DocumentMove'; import UiStore from 'stores/UiStore'; import DocumentsStore from 'stores/DocumentsStore'; +import CollectionsStore from 'stores/CollectionsStore'; import DocumentMenu from 'menus/DocumentMenu'; import SaveAction from './components/SaveAction'; import LoadingPlaceholder from 'components/LoadingPlaceholder'; @@ -45,6 +46,7 @@ type Props = { location: Object, keydown: Object, documents: DocumentsStore, + collections: CollectionsStore, newDocument?: boolean, ui: UiStore, }; @@ -213,7 +215,9 @@ type Props = { const isMoving = this.props.match.path === matchDocumentMove; const document = this.document; const isFetching = !document; - const titleText = get(document, 'title', ''); + const titleText = + get(document, 'title', '') || + this.props.collections.titleForDocument(this.props.location.pathname); if (this.notFound) { return this.renderNotFound(); @@ -362,4 +366,6 @@ const StyledDropToImport = styled(DropToImport)` flex: 1; `; -export default withRouter(inject('ui', 'user', 'documents')(DocumentScene)); +export default withRouter( + inject('ui', 'user', 'documents', 'collections')(DocumentScene) +); diff --git a/frontend/stores/CollectionsStore.js b/frontend/stores/CollectionsStore.js index 6b5d2d997..891206c19 100644 --- a/frontend/stores/CollectionsStore.js +++ b/frontend/stores/CollectionsStore.js @@ -25,6 +25,7 @@ type Options = { type DocumentPathItem = { id: string, title: string, + url: string, type: 'document' | 'collection', }; @@ -59,16 +60,16 @@ class CollectionsStore { let results = []; const travelDocuments = (documentList, path) => documentList.forEach(document => { - const { id, title } = document; - const node = { id, title, type: 'document' }; + const { id, title, url } = document; + const node = { id, title, url, type: 'document' }; results.push(_.concat(path, node)); travelDocuments(document.children, _.concat(path, [node])); }); if (this.isLoaded) { this.data.forEach(collection => { - const { id, name } = collection; - const node = { id, title: name, type: 'collection' }; + const { id, name, url } = collection; + const node = { id, title: name, url, type: 'collection' }; results.push([node]); travelDocuments(collection.documents, [node]); }); @@ -87,6 +88,11 @@ class CollectionsStore { return this.pathsToDocuments.find(path => path.id === documentId); } + titleForDocument(documentUrl: string): ?string { + const path = this.pathsToDocuments.find(path => path.url === documentUrl); + if (path) return path.title; + } + /* Actions */ @action fetchAll = async (): Promise<*> => { From 6959cec1bfcd0e15ef216103ed7338d36447c4a9 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Sun, 22 Oct 2017 11:22:25 -0700 Subject: [PATCH 08/19] improved code readability --- .../components/Editor/plugins/MarkdownShortcuts.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/components/Editor/plugins/MarkdownShortcuts.js b/frontend/components/Editor/plugins/MarkdownShortcuts.js index 2431b973e..10fad8247 100644 --- a/frontend/components/Editor/plugins/MarkdownShortcuts.js +++ b/frontend/components/Editor/plugins/MarkdownShortcuts.js @@ -78,13 +78,16 @@ export default function MarkdownShortcuts() { 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 && - (start === 0 || - end === text.length || - [text.slice(start - 1, start), text.slice(end, end + 1)].includes( - ' ' - )) + (beginningOfBlock || endOfBlock || surroundedByWhitespaces) ) inlineTags.push(i); } From 28fa5f2e3526c60c4115926864cf9164fc4c1b18 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Sun, 22 Oct 2017 20:17:54 -0700 Subject: [PATCH 09/19] Allow toggling sidebar links --- frontend/components/Icon/Icon.js | 3 + .../Layout/components/SidebarCollections.js | 34 +++++----- .../Layout/components/SidebarLink.js | 62 ++++++++++++++----- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/frontend/components/Icon/Icon.js b/frontend/components/Icon/Icon.js index 23619a08f..46bf1acbc 100644 --- a/frontend/components/Icon/Icon.js +++ b/frontend/components/Icon/Icon.js @@ -9,6 +9,7 @@ export type Props = { primary?: boolean, color?: string, size?: number, + onClick?: Function, }; type BaseProps = { @@ -18,6 +19,7 @@ type BaseProps = { export default function Icon({ children, className, + onClick, ...rest }: Props & BaseProps) { const size = rest.size ? rest.size + 'px' : '24px'; @@ -36,6 +38,7 @@ export default function Icon({ viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" className={className} + onClick={onClick} > {children} diff --git a/frontend/components/Layout/components/SidebarCollections.js b/frontend/components/Layout/components/SidebarCollections.js index 0f2242508..7cb3e992a 100644 --- a/frontend/components/Layout/components/SidebarCollections.js +++ b/frontend/components/Layout/components/SidebarCollections.js @@ -182,27 +182,27 @@ const DocumentLink = observer( > 0} - expanded={showChildren} + expand={showChildren} + expandedContent={ + document.children.length + ? + {document.children.map(childDocument => ( + + ))} + + : undefined + } > {document.title} - - {showChildren && - - {document.children && - document.children.map(childDocument => ( - - ))} - } ); } diff --git a/frontend/components/Layout/components/SidebarLink.js b/frontend/components/Layout/components/SidebarLink.js index b3c5456a4..5a349fd47 100644 --- a/frontend/components/Layout/components/SidebarLink.js +++ b/frontend/components/Layout/components/SidebarLink.js @@ -1,5 +1,7 @@ // @flow -import React from 'react'; +import React, { Component } from 'react'; +import { observable, action } from 'mobx'; +import { observer } from 'mobx-react'; import { NavLink } from 'react-router-dom'; import { color, fontWeight } from 'styles/constants'; import styled from 'styled-components'; @@ -54,22 +56,54 @@ type Props = { onClick?: SyntheticEvent => *, children?: React$Element<*>, icon?: React$Element<*>, - hasChildren?: boolean, - expanded?: boolean, + expand?: boolean, + expandedContent?: React$Element<*>, }; -function SidebarLink({ icon, children, expanded, ...rest }: Props) { - const Component = styleComponent(rest.to ? NavLink : StyleableDiv); +@observer class SidebarLink extends Component { + props: Props; - return ( - - - {icon && {icon}} - {rest.hasChildren && } - {children} - - - ); + componentDidMount() { + if (this.props.expand) this.handleExpand(); + } + + componentDidReceiveProps(nextProps: Props) { + if (nextProps.expand) this.handleExpand(); + } + + @observable expanded: boolean = false; + + @action handleClick = (event: SyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); + this.expanded = !this.expanded; + }; + + @action handleExpand = () => { + this.expanded = true; + }; + + render() { + const { icon, children, expandedContent, ...rest } = this.props; + const Component = styleComponent(rest.to ? NavLink : StyleableDiv); + + return ( + + + {icon && {icon}} + {expandedContent && + } + {children} + + {this.expanded && expandedContent} + + ); + } } const Content = styled.div` From c6537279ba85fb2f3a356b8841e6657aa1be8862 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Sun, 22 Oct 2017 22:20:53 -0700 Subject: [PATCH 10/19] Fixes for empty document --- frontend/components/Editor/Editor.js | 2 +- server/presenters/document.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 6a99e730f..659cb4b20 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -51,7 +51,7 @@ type KeyData = { onImageUploadStop: props.onImageUploadStop, }); - if (props.text) { + if (props.text.trim().length) { this.state = { state: Markdown.deserialize(props.text) }; } else { this.state = { state: Plain.deserialize('') }; diff --git a/server/presenters/document.js b/server/presenters/document.js index bf483cd1d..575d18f3b 100644 --- a/server/presenters/document.js +++ b/server/presenters/document.js @@ -14,6 +14,11 @@ async function present(ctx: Object, document: Document, options: ?Options) { ...options, }; ctx.cache.set(document.id, document); + + // For empty document content, return the title + if (document.text.trim().length === 0) + document.text = `# ${document.title || 'Untitled document'}`; + const data = { id: document.id, url: document.getUrl(), From 0129493ca168b261183ea703fe1b5f0fc99fec67 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 23 Oct 2017 07:35:45 -0700 Subject: [PATCH 11/19] Change theme --- frontend/styles/prism.css | 195 ++++++++++++++++++++++++-------------- 1 file changed, 125 insertions(+), 70 deletions(-) diff --git a/frontend/styles/prism.css b/frontend/styles/prism.css index 3655ef339..c7e3f21ff 100644 --- a/frontend/styles/prism.css +++ b/frontend/styles/prism.css @@ -1,118 +1,173 @@ -/** - * GHColors theme by Avi Aryan (http://aviaryan.in) - * Inspired by Github syntax coloring - */ +/* +Name: Base16 Atelier Sulphurpool Light +Author: Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) + +Prism template by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/prism/) +Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ code[class*="language-"], pre[class*="language-"] { - color: #393A34; - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - font-size: 13px; - line-height: 1.4em; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 13px; + line-height: 1.375; + direction: ltr; + font-weight: 600; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + background: #f5f7ff; + color: #5e6687; } pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { - background: #b3d4fc; + text-shadow: none; + background: #dfe2f1; } pre[class*="language-"]::selection, pre[class*="language-"] ::selection, code[class*="language-"]::selection, code[class*="language-"] ::selection { - background: #b3d4fc; + text-shadow: none; + background: #dfe2f1; } /* Code blocks */ pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; - border: 1px solid #dddddd; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; } /* Inline code */ :not(pre) > code[class*="language-"] { - padding: .2em; - padding-top: 1px; padding-bottom: 1px; - background: #f8f8f8; - border: 1px solid #dddddd; + padding: .1em; + border-radius: .3em; } .token.comment, .token.prolog, .token.doctype, .token.cdata { - color: #999988; font-style: italic; + color: #898ea4; +} + +.token.punctuation { + color: #5e6687; } .token.namespace { - opacity: .7; + opacity: .7; } -.token.string, -.token.attr-value { - color: #e3116c; +.token.operator, +.token.boolean, +.token.number { + color: #c76b29; } -.token.punctuation, -.token.operator { - color: #393A34; /* no highlight */ + +.token.property { + color: #c08b30; +} + +.token.tag { + color: #3d8fd1; +} + +.token.string { + color: #22a2c9; +} + +.token.selector { + color: #6679cc; +} + +.token.attr-name { + color: #c76b29; } .token.entity, .token.url, -.token.symbol, -.token.number, -.token.boolean, -.token.variable, -.token.constant, -.token.property, -.token.regex, -.token.inserted { - color: #36acaa; +.language-css .token.string, +.style .token.string { + color: #22a2c9; } -.token.atrule, +.token.attr-value, .token.keyword, -.token.attr-name, -.language-autohotkey .token.selector { - color: #00a4db; +.token.control, +.token.directive, +.token.unit { + color: #ac9739; } -.token.function, -.token.deleted, -.language-autohotkey .token.tag { - color: #9a050f; +.token.statement, +.token.regex, +.token.atrule { + color: #22a2c9; } -.token.tag, -.token.selector, -.language-autohotkey .token.keyword { - color: #00009f; +.token.placeholder, +.token.variable { + color: #3d8fd1; } -.token.important, -.token.function, -.token.bold { - font-weight: bold; +.token.deleted { + text-decoration: line-through; +} + +.token.inserted { + border-bottom: 1px dotted #202746; + text-decoration: none; } .token.italic { - font-style: italic; + font-style: italic; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.important { + color: #c94922; +} + +.token.entity { + cursor: help; +} + +pre > code.highlight { + outline: 0.4em solid #c94922; + outline-offset: .4em; +} + +/* overrides color-values for the Line Numbers plugin + * http://prismjs.com/plugins/line-numbers/ + */ +.line-numbers .line-numbers-rows { + border-right-color: #dfe2f1; +} + +.line-numbers-rows > span:before { + color: #979db4; +} + +/* overrides color-values for the Line Highlight plugin + * http://prismjs.com/plugins/line-highlight/ + */ +.line-highlight { + background: rgba(107, 115, 148, 0.2); + background: -webkit-linear-gradient(left, rgba(107, 115, 148, 0.2) 70%, rgba(107, 115, 148, 0)); + background: linear-gradient(to right, rgba(107, 115, 148, 0.2) 70%, rgba(107, 115, 148, 0)); } From e79897e5cb86db49caff84b7c30f013485d30ed6 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 23 Oct 2017 20:13:22 -0700 Subject: [PATCH 12/19] Fixes link editing --- frontend/components/Editor/Editor.js | 9 ++++----- .../Toolbar/components/DocumentResult.js | 4 ++-- .../components/Toolbar/components/LinkToolbar.js | 7 ++++--- frontend/components/Icon/NextIcon.js | 15 +++++++++++++++ 4 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 frontend/components/Icon/NextIcon.js diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 659cb4b20..b73d77388 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { Editor, Plain } from 'slate'; import keydown from 'react-keydown'; -import type { Document, State, Editor as EditorType } from './types'; +import type { State, Editor as EditorType } from './types'; import getDataTransferFiles from 'utils/getDataTransferFiles'; import Flex from 'components/Flex'; import ClickablePadding from './components/ClickablePadding'; @@ -76,10 +76,10 @@ type KeyData = { onChange = (state: State) => { this.setState({ state }); - }; - onDocumentChange = (document: Document, state: State) => { - this.props.onChange(Markdown.serialize(state)); + if (this.state.state !== state) { + this.props.onChange(Markdown.serialize(state)); + } }; handleDrop = async (ev: SyntheticEvent) => { @@ -205,7 +205,6 @@ type KeyData = { state={this.state.state} onKeyDown={this.onKeyDown} onChange={this.onChange} - onDocumentChange={this.onDocumentChange} onSave={onSave} readOnly={readOnly} /> diff --git a/frontend/components/Editor/components/Toolbar/components/DocumentResult.js b/frontend/components/Editor/components/Toolbar/components/DocumentResult.js index 8351681d6..7d4b157b0 100644 --- a/frontend/components/Editor/components/Toolbar/components/DocumentResult.js +++ b/frontend/components/Editor/components/Toolbar/components/DocumentResult.js @@ -3,7 +3,7 @@ import React from 'react'; import styled from 'styled-components'; import { fontWeight, color } from 'styles/constants'; import Document from 'models/Document'; -import GoToIcon from 'components/Icon/GoToIcon'; +import NextIcon from 'components/Icon/NextIcon'; type Props = { innerRef?: Function, @@ -14,7 +14,7 @@ type Props = { function DocumentResult({ document, ...rest }: Props) { return ( - + {document.title} ); diff --git a/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js b/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js index 010048344..c7b38bb24 100644 --- a/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js +++ b/frontend/components/Editor/components/Toolbar/components/LinkToolbar.js @@ -114,9 +114,10 @@ class LinkToolbar extends Component { const { state } = this.props; const transform = state.transform(); - if (state.selection.isExpanded) { + if (href) { + transform.setInline({ type: 'link', data: { href } }); + } else { transform.unwrapInline('link'); - if (href) transform.wrapInline({ type: 'link', data: { href } }); } this.props.onChange(transform.apply()); @@ -179,7 +180,7 @@ class LinkToolbar extends Component { } const SearchResults = styled.div` - background: rgba(34, 34, 34, .95); + background: #2F3336; position: absolute; top: 100%; width: 100%; diff --git a/frontend/components/Icon/NextIcon.js b/frontend/components/Icon/NextIcon.js new file mode 100644 index 000000000..ab9144dc6 --- /dev/null +++ b/frontend/components/Icon/NextIcon.js @@ -0,0 +1,15 @@ +// @flow +import React from 'react'; +import Icon from './Icon'; +import type { Props } from './Icon'; + +export default function NextIcon(props: Props) { + return ( + + + + ); +} From b43aa7f2f6db3648fd44a8c202fcc4a842b5edc4 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Mon, 23 Oct 2017 21:57:59 -0700 Subject: [PATCH 13/19] Fixed anchor style and removed unused styles --- frontend/components/Editor/Editor.js | 16 ---------------- frontend/components/Editor/components/Heading.js | 1 + 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 659cb4b20..14013f46b 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -246,22 +246,6 @@ const StyledEditor = styled(Editor)` h5, h6 { font-weight: 500; - - .anchor { - visibility: hidden; - color: #dedede; - padding-left: 0.25em; - } - - &:hover { - .anchor { - visibility: visible; - - &:hover { - color: #cdcdcd; - } - } - } } h1:first-of-type { diff --git a/frontend/components/Editor/components/Heading.js b/frontend/components/Editor/components/Heading.js index c8ceb4464..a107a30a6 100644 --- a/frontend/components/Editor/components/Heading.js +++ b/frontend/components/Editor/components/Heading.js @@ -72,6 +72,7 @@ export const Heading1 = styled(Heading)` &:hover { ${Anchor} { visibility: visible; + text-decoration: none; } } `; From 7c3be91556134fc9583387014ee6bb2b3d69393a Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Mon, 23 Oct 2017 22:00:24 -0700 Subject: [PATCH 14/19] adjusted `code` size --- frontend/styles/base.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/styles/base.css b/frontend/styles/base.css index 711104aa5..ec8a87f07 100644 --- a/frontend/styles/base.css +++ b/frontend/styles/base.css @@ -101,11 +101,11 @@ samp { } code, samp { - font-size: 87.5%; + font-size: 85%; padding: 0.125em; } pre { - font-size: 87.5%; + font-size: 85%; overflow: scroll; } blockquote { From a5c1b64900ae6b018a22ea54a95f463de01221b6 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Mon, 23 Oct 2017 23:12:28 -0700 Subject: [PATCH 15/19] Fixes to document drag&drop --- frontend/components/Editor/Editor.js | 5 +- frontend/scenes/Document/Document.js | 151 ++++++++++----------------- 2 files changed, 57 insertions(+), 99 deletions(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index b73d77388..7d158112a 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -83,6 +83,7 @@ type KeyData = { }; handleDrop = async (ev: SyntheticEvent) => { + if (this.props.readOnly) return; // check if this event was already handled by the Editor if (ev.isDefaultPrevented()) return; @@ -92,7 +93,9 @@ type KeyData = { const files = getDataTransferFiles(ev); for (const file of files) { - await this.insertImageFile(file); + if (file.type.startsWith('image/')) { + await this.insertImageFile(file); + } } }; diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index d290ad791..ed517e30d 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -27,7 +27,6 @@ import DocumentMenu from 'menus/DocumentMenu'; import SaveAction from './components/SaveAction'; import LoadingPlaceholder from 'components/LoadingPlaceholder'; import Editor from 'components/Editor'; -import DropToImport from 'components/DropToImport'; import LoadingIndicator from 'components/LoadingIndicator'; import Collaborators from 'components/Collaborators'; import CenteredContent from 'components/CenteredContent'; @@ -57,7 +56,6 @@ type Props = { @observable editCache: ?string; @observable newDocument: ?Document; - @observable isDragging = false; @observable isLoading = false; @observable isSaving = false; @observable notFound = false; @@ -198,14 +196,6 @@ type Props = { this.props.history.push(url); }; - onStartDragging = () => { - this.isDragging = true; - }; - - onStopDragging = () => { - this.isDragging = false; - }; - renderNotFound() { return ; } @@ -226,11 +216,6 @@ type Props = { return ( {isMoving && document && } - - {this.isDragging && - - Drop files here to import into Atlas. - } {titleText && } {this.isLoading && } {isFetching && @@ -239,73 +224,60 @@ type Props = { } {!isFetching && document && - - - - - - - {!isNew && - !this.isEditing && - } - - {this.isEditing - ? - : - Edit - } - - {this.isEditing && - - Discard - } - {!this.isEditing && - - - } - {!this.isEditing && } - - {!this.isEditing && - - + + + + + + {!isNew && + !this.isEditing && + } + + {this.isEditing + ? + : + Edit } - - - - - } + + {this.isEditing && + + Discard + } + {!this.isEditing && + + + } + {!this.isEditing && } + + {!this.isEditing && + + + } + + + + } ); } @@ -329,18 +301,6 @@ const HeaderAction = styled(Flex)` } `; -const DropHere = styled(Flex)` - pointer-events: none; - position: fixed; - top: 0; - left: ${layout.sidebarWidth}; - bottom: 0; - right: 0; - text-align: center; - background: rgba(255,255,255,.9); - z-index: 1; -`; - const Meta = styled(Flex)` align-items: flex-start; position: fixed; @@ -361,11 +321,6 @@ const LoadingState = styled(LoadingPlaceholder)` margin: 90px 0; `; -const StyledDropToImport = styled(DropToImport)` - display: flex; - flex: 1; -`; - export default withRouter( inject('ui', 'user', 'documents', 'collections')(DocumentScene) ); From 87b3bcba614d0fbb2dd5f38b7e5ec43db4b1cbc6 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 24 Oct 2017 00:08:14 -0700 Subject: [PATCH 16/19] Quick fix for #367 --- frontend/components/Editor/insertImage.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/components/Editor/insertImage.js b/frontend/components/Editor/insertImage.js index 7f59ab5ce..d3e4c6bd7 100644 --- a/frontend/components/Editor/insertImage.js +++ b/frontend/components/Editor/insertImage.js @@ -15,6 +15,7 @@ export default async function insertImageFile( 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; @@ -24,7 +25,7 @@ export default async function insertImageFile( .insertBlock({ type: 'image', isVoid: true, - data: { src, id, loading: true }, + data: { src, id, alt, loading: true }, }) .apply(); editor.onChange(state); @@ -45,7 +46,7 @@ export default async function insertImageFile( ); return finalTransform.setNodeByKey(placeholder.key, { - data: { src, loading: false }, + data: { src, alt, loading: false }, }); } catch (err) { throw err; From 4dfe826ed73d7ce5545a86ccc694012aaede0219 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 24 Oct 2017 07:56:36 -0700 Subject: [PATCH 17/19] Fixed font weight, super githubby style --- frontend/styles/prism.css | 54 ++++++++------------------------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/frontend/styles/prism.css b/frontend/styles/prism.css index c7e3f21ff..6a7ff2455 100644 --- a/frontend/styles/prism.css +++ b/frontend/styles/prism.css @@ -1,19 +1,16 @@ /* -Name: Base16 Atelier Sulphurpool Light -Author: Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) - -Prism template by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/prism/) +Based on Prism template by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/prism/) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ code[class*="language-"], pre[class*="language-"] { + -webkit-font-smoothing: initial; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 13px; line-height: 1.375; direction: ltr; - font-weight: 600; text-align: left; white-space: pre; word-spacing: normal; @@ -25,20 +22,7 @@ pre[class*="language-"] { -moz-hyphens: none; -ms-hyphens: none; hyphens: none; - background: #f5f7ff; - color: #5e6687; -} - -pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, -code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { - text-shadow: none; - background: #dfe2f1; -} - -pre[class*="language-"]::selection, pre[class*="language-"] ::selection, -code[class*="language-"]::selection, code[class*="language-"] ::selection { - text-shadow: none; - background: #dfe2f1; + color: #24292e; } /* Code blocks */ @@ -58,7 +42,7 @@ pre[class*="language-"] { .token.prolog, .token.doctype, .token.cdata { - color: #898ea4; + color: #6a737d; } .token.punctuation { @@ -72,7 +56,7 @@ pre[class*="language-"] { .token.operator, .token.boolean, .token.number { - color: #c76b29; + color: #d73a49; } .token.property { @@ -84,7 +68,7 @@ pre[class*="language-"] { } .token.string { - color: #22a2c9; + color: #032f62; } .token.selector { @@ -107,7 +91,11 @@ pre[class*="language-"] { .token.control, .token.directive, .token.unit { - color: #ac9739; + color: #d73a49; +} + +.token.function { + color: #6f42c1; } .token.statement, @@ -151,23 +139,3 @@ pre > code.highlight { outline: 0.4em solid #c94922; outline-offset: .4em; } - -/* overrides color-values for the Line Numbers plugin - * http://prismjs.com/plugins/line-numbers/ - */ -.line-numbers .line-numbers-rows { - border-right-color: #dfe2f1; -} - -.line-numbers-rows > span:before { - color: #979db4; -} - -/* overrides color-values for the Line Highlight plugin - * http://prismjs.com/plugins/line-highlight/ - */ -.line-highlight { - background: rgba(107, 115, 148, 0.2); - background: -webkit-linear-gradient(left, rgba(107, 115, 148, 0.2) 70%, rgba(107, 115, 148, 0)); - background: linear-gradient(to right, rgba(107, 115, 148, 0.2) 70%, rgba(107, 115, 148, 0)); -} From 23c95f4d56dc416a7ac83686002cc1862d423c1e Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 24 Oct 2017 08:43:35 -0700 Subject: [PATCH 18/19] Fixes: JS error when no heading Fixes: Reduce hover zone Fixes: Add H1s into TOC --- .../components/Editor/components/Contents.js | 40 ++++++++++++------- .../components/Editor/components/Heading.js | 2 +- frontend/components/Editor/headingToSlug.js | 7 ++-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/frontend/components/Editor/components/Contents.js b/frontend/components/Editor/components/Contents.js index 31382cbc7..7a1f19a44 100644 --- a/frontend/components/Editor/components/Contents.js +++ b/frontend/components/Editor/components/Contents.js @@ -26,9 +26,12 @@ type Props = { } updateActiveHeading = () => { - let activeHeading = this.headingElements[0].id; + const elements = this.headingElements; + if (!elements.length) return; - for (const element of this.headingElements) { + let activeHeading = elements[0].id; + + for (const element of elements) { const bounds = element.getBoundingClientRect(); if (bounds.top <= 0) activeHeading = element.id; } @@ -38,7 +41,7 @@ type Props = { get headingElements(): HTMLElement[] { const elements = []; - const tagNames = ['h2', 'h3', 'h4', 'h5', 'h6']; + const tagNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; for (const tagName of tagNames) { for (const ele of document.getElementsByTagName(tagName)) { @@ -54,7 +57,6 @@ type Props = { return state.document.nodes.filter((node: Block) => { if (!node.text) return false; - if (node.type === 'heading1') return false; return node.type.match(/^heading/); }); } @@ -67,7 +69,7 @@ type Props = { {this.headings.map(heading => { - const slug = headingToSlug(heading.type, heading.text); + const slug = headingToSlug(heading); const active = this.activeHeading === slug; return ( @@ -84,6 +86,13 @@ type Props = { } } +const Wrapper = styled.div` + position: fixed; + right: 0; + top: 150px; + z-index: 100; +`; + const Anchor = styled.a` color: ${props => (props.active ? color.slateDark : color.slate)}; font-weight: ${props => (props.active ? 500 : 400)}; @@ -91,6 +100,8 @@ const Anchor = styled.a` transition: all 100ms ease-in-out; margin-right: -5px; padding: 2px 0; + pointer-events: none; + text-overflow: ellipsis; &:hover { color: ${color.primary}; @@ -99,14 +110,15 @@ const Anchor = styled.a` const ListItem = styled.li` position: relative; - margin-left: ${props => (props.type === 'heading2' ? '8px' : '16px')}; + 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 === 'heading2' ? '—' : '–')}"; + content: "${props => (props.type.match(/heading[12]/) ? '—' : '–')}"; position: absolute; right: 0; } @@ -117,21 +129,21 @@ const Sections = styled.ol` 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; } } `; -const Wrapper = styled.div` - position: fixed; - right: 0; - top: 160px; - z-index: 100; -`; - export default Contents; diff --git a/frontend/components/Editor/components/Heading.js b/frontend/components/Editor/components/Heading.js index 0a9891d93..4c3ce8130 100644 --- a/frontend/components/Editor/components/Heading.js +++ b/frontend/components/Editor/components/Heading.js @@ -30,7 +30,7 @@ function Heading(props: Props) { const parentIsDocument = parent instanceof Document; const firstHeading = parentIsDocument && parent.nodes.first() === node; const showPlaceholder = placeholder && firstHeading && !node.text; - const slugish = headingToSlug(node.type, node.text); + const slugish = headingToSlug(node); const showHash = readOnly && !!slugish; const Component = component; const emoji = editor.props.emoji || ''; diff --git a/frontend/components/Editor/headingToSlug.js b/frontend/components/Editor/headingToSlug.js index 1eee75dcd..04b9d6273 100644 --- a/frontend/components/Editor/headingToSlug.js +++ b/frontend/components/Editor/headingToSlug.js @@ -1,8 +1,9 @@ // @flow import { escape } from 'lodash'; +import type { Node } from './types'; import slug from 'slug'; -export default function headingToSlug(heading: string, title: string) { - const level = heading.replace('heading', 'h'); - return escape(`${level}-${slug(title)}`); +export default function headingToSlug(node: Node) { + const level = node.type.replace('heading', 'h'); + return escape(`${level}-${slug(node.text)}-${node.key}`); } From 8a3b4429d47d544c4a852d9dfb44e5ed468fd47c Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 24 Oct 2017 08:49:33 -0700 Subject: [PATCH 19/19] Dont merge on github --- frontend/components/Editor/Editor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 7e814dca4..0f3768812 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -73,9 +73,9 @@ type KeyData = { } } - onChange = (state: State) => { - if (this.editorState !== state) { - this.props.onChange(Markdown.serialize(state)); + onChange = (editorState: State) => { + if (this.editorState !== editorState) { + this.props.onChange(Markdown.serialize(editorState)); } this.editorState = editorState;