diff --git a/Makefile b/Makefile index 93158a0e1..0e2769dcf 100644 --- a/Makefile +++ b/Makefile @@ -27,17 +27,21 @@ dist: ./node_modules $(shell find ./lib) @ $(babel) --out-dir ./dist ./lib @ touch ./dist +# Build the auto-markdown example. +example-auto-markdown: ./node_modules + @ $(browserify) --debug --transform babelify --outfile ./examples/auto-markdown/build.js ./examples/auto-markdown/index.js + # Build the basic example. example-basic: ./node_modules @ $(browserify) --debug --transform babelify --outfile ./examples/basic/build.js ./examples/basic/index.js -# Build the plaintext example. -example-plaintext: ./node_modules - @ $(browserify) --debug --transform babelify --outfile ./examples/plaintext/build.js ./examples/plaintext/index.js +# Build the plain-text example. +example-plain-text: ./node_modules + @ $(browserify) --debug --transform babelify --outfile ./examples/plain-text/build.js ./examples/plain-text/index.js -# Build the richtext example. -example-richtext: ./node_modules - @ $(browserify) --debug --transform babelify --outfile ./examples/richtext/build.js ./examples/richtext/index.js +# Build the rich-text example. +example-rich-text: ./node_modules + @ $(browserify) --debug --transform babelify --outfile ./examples/rich-text/build.js ./examples/rich-text/index.js # Lint the sources files with Standard JS. lint: ./node_modules @@ -63,17 +67,21 @@ test-browser: ./node_modules ./test/support/build.js test-server: ./node_modules @ $(mocha) --reporter spec --timeout 5000 ./test/server.js +# Watch the auto-markdown example. +watch-example-auto-markdown: ./node_modules + @ $(MAKE) example-auto-markdown browserify=$(watchify) + # Watch the basic example. watch-example-basic: ./node_modules @ $(MAKE) example-basic browserify=$(watchify) -# Watch the plaintext example. -watch-example-plaintext: ./node_modules - @ $(MAKE) example-plaintext browserify=$(watchify) +# Watch the plain-text example. +watch-example-plain-text: ./node_modules + @ $(MAKE) example-plain-text browserify=$(watchify) -# Watch the richtext example. -watch-example-richtext: ./node_modules - @ $(MAKE) example-richtext browserify=$(watchify) +# Watch the rich-text example. +watch-example-rich-text: ./node_modules + @ $(MAKE) example-rich-text browserify=$(watchify) # Phony targets. .PHONY: examples diff --git a/examples/richtext/index.css b/examples/auto-markdown/index.css similarity index 100% rename from examples/richtext/index.css rename to examples/auto-markdown/index.css diff --git a/examples/auto-markdown/index.html b/examples/auto-markdown/index.html new file mode 100644 index 000000000..2ee7e77dd --- /dev/null +++ b/examples/auto-markdown/index.html @@ -0,0 +1,12 @@ + + + + Editor | Auto-markdown Example + + + + +
+ + + diff --git a/examples/auto-markdown/index.js b/examples/auto-markdown/index.js new file mode 100644 index 000000000..a09ad523f --- /dev/null +++ b/examples/auto-markdown/index.js @@ -0,0 +1,187 @@ + +import Editor, { Mark, Raw } from '../..' +import React from 'react' +import ReactDOM from 'react-dom' +import keycode from 'keycode' + +/** + * State. + */ + +const state = { + nodes: [ + { + type: 'paragraph', + nodes: [ + { + type: 'text', + ranges: [ + { + text: 'Since it\'s rich text, you can do things like turn a selection of text ', + } + ] + } + ] + }, + { + type: 'block-quote', + nodes: [ + { + type: 'text', + ranges: [ + { + text: 'A wise quote.' + } + ] + } + ] + }, + { + type: 'paragraph', + nodes: [ + { + type: 'text', + ranges: [ + { + text: 'Try it out for yourself!' + } + ] + } + ] + } + ] +} + +/** + * App. + */ + +class App extends React.Component { + + state = { + state: Raw.deserialize(state) + }; + + render() { + return ( +
+ this.renderNode(node)} + renderMark={mark => this.renderMark(mark)} + onKeyDown={(e, state) => this.onKeyDown(e, state)} + onChange={(state) => { + console.groupCollapsed('Change!') + console.log('Document:', state.document.toJS()) + console.log('Selection:', state.selection.toJS()) + console.log('Content:', Raw.serialize(state)) + console.groupEnd() + this.setState({ state }) + }} + /> +
+ ) + } + + renderNode(node) { + switch (node.type) { + case 'block-quote': { + return (props) =>
{props.children}
+ } + case 'bulleted-list': { + return (props) => + } + case 'heading-one': { + return (props) =>

{props.children}

+ } + case 'heading-two': { + return (props) =>

{props.children}

+ } + case 'heading-three': { + return (props) =>

{props.children}

+ } + case 'heading-four': { + return (props) =>

{props.children}

+ } + case 'heading-five': { + return (props) =>
{props.children}
+ } + case 'heading-six': { + return (props) =>
{props.children}
+ } + case 'list-item': { + return (props) =>
  • {props.chidlren}
  • + } + case 'numbered-list': { + return (props) =>
      {props.children}
    + } + case 'paragraph': { + return (props) =>

    {props.children}

    + } + } + } + + onKeyDown(e, state) { + const key = keycode(e.which) + if (key != 'space') return + if (state.isCurrentlyExpanded) return + let { selection } = state + const { currentTextNodes, document } = state + const { startOffset } = selection + const node = currentTextNodes.first() + const { text } = node + const chars = text.slice(0, startOffset).replace(/\s*/g, '') + let transform = state.transform() + + switch (chars) { + case '#': + transform = transform.setType('header-one') + break + case '##': + transform = transform.setType('header-two') + break + case '###': + transform = transform.setType('header-three') + break + case '####': + transform = transform.setType('header-four') + break + case '#####': + transform = transform.setType('header-five') + break + case '######': + transform = transform.setType('header-six') + break + case '>': + transform = transform.setType('block-quote') + break + case '-': + transform = transform.setType('list-item') + if (wrapper.type == 'paragraph') transform = transform.setType('bulleted-list') + if (wrapper.type == 'bulleted-list') transform = transform.wrap('list-item') + if (wrapper.type == 'list-item') transform = transform.wrap('unordered-list') + break + default: + return + } + + + state = transform + .deleteAtRange(selection.extendBackwardToStartOf(node)) + .apply() + + selection = selection.moveToStartOf(node) + state = state.merge({ selection }) + e.preventDefault() + return state + } + +} + +/** + * Attach. + */ + +const app = +const root = document.body.querySelector('main') +ReactDOM.render(app, root) diff --git a/examples/plaintext/index.css b/examples/plain-text/index.css similarity index 100% rename from examples/plaintext/index.css rename to examples/plain-text/index.css diff --git a/examples/plaintext/index.html b/examples/plain-text/index.html similarity index 79% rename from examples/plaintext/index.html rename to examples/plain-text/index.html index 869565479..811089bbf 100644 --- a/examples/plaintext/index.html +++ b/examples/plain-text/index.html @@ -1,7 +1,7 @@ - Editor | Plaintext Example + Editor | Plain Text Example diff --git a/examples/plaintext/index.js b/examples/plain-text/index.js similarity index 100% rename from examples/plaintext/index.js rename to examples/plain-text/index.js diff --git a/examples/rich-text/index.css b/examples/rich-text/index.css new file mode 100644 index 000000000..0c9a20b69 --- /dev/null +++ b/examples/rich-text/index.css @@ -0,0 +1,56 @@ + +html { + background: #eee; + padding: 20px; +} + +main { + background: #fff; + padding: 10px; + max-width: 40em; + margin: 0 auto; +} + +p { + margin: 0; +} + +blockquote { + border-left: 2px solid #ddd; + margin-left: 0; + padding-left: 10px; + color: #aaa; + font-style: italic; +} + +.editor > * > * + * { + margin-top: 1em; +} + +.menu { + margin: 0 -10px; + padding: 1px 0 9px 8px; + border-bottom: 2px solid #eee; + margin-bottom: 10px; +} + +.menu > * { + display: inline-block; +} + +.menu > * + * { + margin-left: 10px; +} + +.button { + color: #ccc; + cursor: pointer; +} + +.button[data-active="true"] { + color: black; +} + +.material-icons { + font-size: 18px; +} diff --git a/examples/richtext/index.html b/examples/rich-text/index.html similarity index 85% rename from examples/richtext/index.html rename to examples/rich-text/index.html index a0b66ac19..66be0c316 100644 --- a/examples/richtext/index.html +++ b/examples/rich-text/index.html @@ -1,7 +1,7 @@ - Editor | Richtext Example + Editor | Rich Text Example diff --git a/examples/richtext/index.js b/examples/rich-text/index.js similarity index 84% rename from examples/richtext/index.js rename to examples/rich-text/index.js index a97bbd999..ef768ad42 100644 --- a/examples/richtext/index.js +++ b/examples/rich-text/index.js @@ -54,6 +54,55 @@ const state = { ] } ] + }, + { + type: 'paragraph', + nodes: [ + { + type: 'text', + ranges: [ + { + text: 'Since it\'s rich text, you can do things like turn a selection of text ', + }, + { + text: 'bold', + marks: [ + { + type: 'bold' + } + ] + },{ + text: ', or add a semanticlly rendered block quote in the middle of the page, like this:' + } + ] + } + ] + }, + { + type: 'block-quote', + nodes: [ + { + type: 'text', + ranges: [ + { + text: 'A wise quote.' + } + ] + } + ] + }, + { + type: 'paragraph', + nodes: [ + { + type: 'text', + ranges: [ + { + text: 'Try it out for yourself!' + } + ] + } + ] } ] } diff --git a/examples/test/index.html b/examples/test/index.html new file mode 100644 index 000000000..4b9fa08f3 --- /dev/null +++ b/examples/test/index.html @@ -0,0 +1,39 @@ + + + + Editor | Basic Example + + + +
    +
    +

    + Some text. +

    +

    + Some text. +

    +
    +

    + Some text. +

    +

    + Some text. +

    +
    +

    +

    + + + + + + + + + +
    1one
    2two
    +
    +
    + + diff --git a/lib/models/state.js b/lib/models/state.js index 747d72aab..5b23d7360 100644 --- a/lib/models/state.js +++ b/lib/models/state.js @@ -53,6 +53,26 @@ class State extends Record(DEFAULTS) { return new State(properties) } + /** + * Is the current selection collapsed? + * + * @return {Boolean} isCollapsed + */ + + get isCurrentlyCollapsed() { + return this.selection.isCollapsed + } + + /** + * Is the current selection expanded? + * + * @return {Boolean} isExpanded + */ + + get isCurrentlyExpanded() { + return this.selection.isExpanded + } + /** * Get the characters in the current selection. * @@ -60,8 +80,7 @@ class State extends Record(DEFAULTS) { */ get currentCharacters() { - const { document, selection } = this - return document.getCharactersAtRange(selection) + return this.document.getCharactersAtRange(this.selection) } /** @@ -71,8 +90,7 @@ class State extends Record(DEFAULTS) { */ get currentMarks() { - const { document, selection } = this - return document.getMarksAtRange(selection) + return this.document.getMarksAtRange(this.selection) } /** @@ -82,8 +100,7 @@ class State extends Record(DEFAULTS) { */ get currentWrappingNodes() { - const { document, selection, textNodes } = this - return document.getWrappingNodesAtRange(selection) + return this.document.getWrappingNodesAtRange(this.selection) } /** @@ -93,8 +110,7 @@ class State extends Record(DEFAULTS) { */ get currentTextNodes() { - const { document, selection } = this - return document.getTextNodesAtRange(selection) + return this.document.getTextNodesAtRange(this.selection) } /**