@the-grid/ed
Advanced tools
Comparing version 0.5.0 to 0.6.0
## dev | ||
## 0.6.0 - 2016-03-16 | ||
* Placeholder progress bar | ||
* `progress` 0-100 with `ed.updatePlaceholder(id, status, progress)` or `ed.setContent([{id, metadata: {status, progress}}])` | ||
* NEW -- **X** button on the Placeholder component triggers `options.onPlaceholderCancel(id)` | ||
* When that is hit, `ed.getContent()` will already have removed the cancelled placeholder block | ||
* Delete from under media block ignored | ||
## 0.5.0 - The Fold - 2016-03-15 | ||
@@ -15,2 +23,3 @@ | ||
* BREAKING -- `options.[menuBar|menuTip]` made camelCase | ||
* BREAKING -- no more `options.onAutosave`: apps will do that logic | ||
@@ -17,0 +26,0 @@ ## 0.4.7 - 2016-03-01 |
@@ -30,2 +30,3 @@ /* eslint no-console: 0 */ | ||
, onShareUrl: onShareUrlDemo | ||
, onPlaceholderCancel: onPlaceholderCancelDemo | ||
, imgfloConfig: null | ||
@@ -75,3 +76,3 @@ } | ||
ids.forEach(function (id, index) { | ||
let status = `${percent}% done uploading ${names[index]}` | ||
let status = `Uploading ${names[index]}` | ||
ed.updatePlaceholder(id, status, percent) | ||
@@ -113,3 +114,3 @@ }) | ||
function (percent) { | ||
const status = `${percent}% done sharing ${url}` | ||
const status = `Sharing ${url}` | ||
ed.updatePlaceholder(block, status, percent) | ||
@@ -139,3 +140,3 @@ }, | ||
let animate = function () { | ||
percent++ | ||
percent += 0.5 | ||
if (percent < 100) { | ||
@@ -236,1 +237,5 @@ // Loop animation | ||
function onPlaceholderCancelDemo (id) { | ||
console.log(`App would cancel the share or upload with id: ${id}`) | ||
} |
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "the grid api with prosemirror", | ||
@@ -8,0 +8,0 @@ "main": "dist/ed.js", |
@@ -66,2 +66,6 @@ `npm start` | ||
/* On share / measurement finishing, app replaces placeholder blocks with ed.setContent */ | ||
}, | ||
onPlaceholderCancel: function (id) { | ||
/* Ed removed the placeholder if you call ed.getContent() now */ | ||
/* App should cancel the share or upload */ | ||
} | ||
@@ -68,0 +72,0 @@ }) |
@@ -18,3 +18,2 @@ import {nodeAboveSelection, onBlankLine} from '../util/pm' | ||
, class: 'EdMenuText' | ||
, select: 'disable' | ||
, display: | ||
@@ -21,0 +20,0 @@ { type: 'label' |
import {CommandSet} from 'prosemirror/src/edit' | ||
import edCommands from './ed-commands' | ||
import {joinBackward} from './schema-commands' | ||
@@ -10,2 +11,3 @@ let commands = CommandSet.default | ||
, selectParentNode: {menu: null} | ||
, joinBackward | ||
} | ||
@@ -12,0 +14,0 @@ ) |
@@ -0,4 +1,72 @@ | ||
// Disable backspace from under media block to delete | ||
// Modified from prosemirror/src/edit/base_commands.js | ||
import {findSelectionFrom} from 'prosemirror/src/edit/selection' | ||
/*eslint-disable */ | ||
export const joinBackward = { | ||
label: 'Join with the block above', | ||
run (pm) { | ||
let {head, empty} = pm.selection | ||
if (!empty || head.offset > 0) return false | ||
// Find the node before this one | ||
let before, cut | ||
for (let i = head.path.length - 1; !before && i >= 0; i--) if (head.path[i] > 0) { | ||
cut = head.shorten(i) | ||
before = pm.doc.path(cut.path).child(cut.offset - 1) | ||
} | ||
// If there is no node before this, try to lift | ||
if (!before) | ||
return pm.tr.lift(head).apply(pm.apply.scroll) | ||
// If the node below has no content and the node above is | ||
// selectable, delete the node below and select the one above. | ||
if (before.type.contains == null && before.type.selectable && pm.doc.path(head.path).size == 0) { | ||
let tr = pm.tr.delete(cut, cut.move(1)).apply(pm.apply.scroll) | ||
pm.setNodeSelection(cut.move(-1)) | ||
return tr | ||
} | ||
// ADDED If the node is media, suppress delete | ||
if (before.type.name === 'media') | ||
return | ||
// If the node doesn't allow children, delete it | ||
if (before.type.contains == null) | ||
return pm.tr.delete(cut.move(-1), cut).apply(pm.apply.scroll) | ||
// Apply the joining algorithm | ||
return deleteBarrier(pm, cut) | ||
}, | ||
keys: ['Backspace(30)', 'Mod-Backspace(30)'] | ||
} | ||
function deleteBarrier (pm, cut) { | ||
let around = pm.doc.path(cut.path) | ||
let before = around.child(cut.offset - 1), after = around.child(cut.offset) | ||
if (before.type.canContainContent(after.type)) { | ||
let tr = pm.tr.join(cut) | ||
if (tr.steps.length && before.size == 0 && !before.sameMarkup(after)) | ||
tr.setNodeType(cut.move(-1), after.type, after.attrs) | ||
if (tr.apply(pm.apply.scroll) !== false) | ||
return | ||
} | ||
let conn | ||
if (after.isTextblock && (conn = before.type.findConnection(after.type))) { | ||
let tr = pm.tr, end = cut.move(1) | ||
tr.step('ancestor', cut, end, null, {types: [before.type, ...conn], | ||
attrs: [before.attrs, ...conn.map(() => null)]}) | ||
tr.join(end) | ||
tr.join(cut) | ||
if (tr.apply(pm.apply.scroll) !== false) return | ||
} | ||
let selAfter = findSelectionFrom(pm.doc, cut, 1) | ||
return pm.tr.lift(selAfter.from, selAfter.to).apply(pm.apply.scroll) | ||
} | ||
/*eslint-enable */ | ||
// import {Media} from '../schema/media' | ||
// // Copied from https://github.com/ProseMirror/prosemirror/blob/1ce4ad1f8f9028e1efa8af5d48ecb28cdca1f800/src/edit/base_commands.js#L485 | ||
@@ -5,0 +73,0 @@ // function nodeAboveSelection (pm) { |
@@ -22,7 +22,7 @@ import React, {createElement as el} from 'react' | ||
store.on('fold.media.change', (block) => { | ||
this.setState({block, id: block.id}) | ||
this.setState({block}) | ||
}) | ||
} | ||
render () { | ||
const {block, id} = this.state | ||
const {block} = this.state | ||
return el('div' | ||
@@ -37,3 +37,3 @@ , { className: 'FoldMedia' | ||
, (block | ||
? el(Media, {initialBlock: block, id}) | ||
? el(Media, {initialBlock: block, id: block.id}) | ||
: this.renderAddMedia() | ||
@@ -40,0 +40,0 @@ ) |
@@ -1,2 +0,2 @@ | ||
import {createElement as el} from 'react' | ||
import React, {createElement as el} from 'react' | ||
@@ -6,2 +6,3 @@ import Placeholder from './placeholder' | ||
import Title from './title' | ||
import rebassTheme from './rebass-theme' | ||
@@ -14,6 +15,26 @@ const Components = | ||
export default function Media (props) { | ||
const {type} = props.initialBlock | ||
let Component = Components[type] || Components.attribution | ||
return el(Component, props) | ||
class Media extends React.Component { | ||
getChildContext () { | ||
return ( | ||
{ imgfloConfig: (this.context.imgfloConfig || this.props.imgfloConfig) | ||
, store: (this.context.store || this.props.store) | ||
, rebass: rebassTheme | ||
} | ||
) | ||
} | ||
render () { | ||
const {type} = this.props.initialBlock | ||
let Component = Components[type] || Components.attribution | ||
return el(Component, this.props) | ||
} | ||
} | ||
Media.contextTypes = | ||
{ imgfloConfig: React.PropTypes.object | ||
, store: React.PropTypes.object | ||
} | ||
Media.childContextTypes = | ||
{ imgfloConfig: React.PropTypes.object | ||
, rebass: React.PropTypes.object | ||
, store: React.PropTypes.object | ||
} | ||
export default React.createFactory(Media) |
@@ -1,12 +0,14 @@ | ||
import {createElement as el} from 'react' | ||
import React, {createElement as el} from 'react' | ||
import Message from 'rebass/dist/Message' | ||
import Progress from 'rebass/dist/Progress' | ||
import Space from 'rebass/dist/Space' | ||
import Close from 'rebass/dist/Close' | ||
export default function Placeholder (props) { | ||
const {metadata} = props.initialBlock | ||
export default function Placeholder (props, context) { | ||
const {store} = context | ||
const {metadata, id} = props.initialBlock | ||
if (!metadata) { | ||
return el('div', {className: 'Placeholder'}) | ||
} | ||
const status = metadata.status || '' | ||
// const value = metadata.progress | ||
// const mode = metadata.progress != null ? 'determinate' : 'indeterminate' | ||
const {status, progress} = metadata | ||
@@ -16,9 +18,33 @@ return el('div' | ||
, el(Message | ||
, { className: 'Placeholder-status' | ||
, theme: 'info' | ||
, { theme: 'info' | ||
, style: {marginBottom: 0} | ||
} | ||
, status | ||
, el('span', {className: 'Placeholder-status'}, status) | ||
, el(Space | ||
, {auto: true, x: 1} | ||
) | ||
, el(Close | ||
, {onClick: makeCancel(store, id)} | ||
) | ||
) | ||
, makeProgress(progress) | ||
) | ||
} | ||
Placeholder.contextTypes = | ||
{ store: React.PropTypes.object } | ||
function makeProgress (progress) { | ||
if (progress == null) return | ||
return el(Progress | ||
, { value: progress / 100 | ||
, theme: 'info' | ||
, style: {marginTop: 16} | ||
} | ||
) | ||
} | ||
function makeCancel (store, id) { | ||
return function () { | ||
store.routeChange('PLACEHOLDER_CANCEL', id) | ||
} | ||
} |
@@ -12,3 +12,5 @@ import {createElement as el} from 'react' | ||
function noop () {} | ||
export default class Ed { | ||
@@ -50,2 +52,3 @@ constructor (options) { | ||
this.onShareFile = options.onShareFile | ||
this.onPlaceholderCancel = options.onPlaceholderCancel || noop | ||
@@ -106,4 +109,7 @@ // Setup main DOM structure | ||
break | ||
case 'PLACEHOLDER_CANCEL': | ||
this._placeholderCancel(payload) | ||
break | ||
default: | ||
break | ||
throw new Error(`ed.routeChange '${type}' does not exist`) | ||
} | ||
@@ -238,2 +244,19 @@ } | ||
} | ||
_placeholderCancel (id) { | ||
let block = this.getBlock(id) | ||
if (!block) { | ||
throw new Error('Can not cancel this placeholder block') | ||
} | ||
if (block.type !== 'placeholder') { | ||
throw new Error('Block is not a placeholder block') | ||
} | ||
const content = this.getContent() | ||
const index = getIndexWithId(content, id) | ||
// MUTATION | ||
content.splice(index, 1) | ||
// Render | ||
this._setMergedContent(content) | ||
// Event | ||
this.onPlaceholderCancel(id) | ||
} | ||
getContent () { | ||
@@ -259,6 +282,4 @@ const doc = this.pm.getContent() | ||
const {media, content} = determineFold(mergedContent) | ||
if (media) { | ||
this._foldMedia = media.id | ||
this.trigger('fold.media.change', media) | ||
} | ||
this._foldMedia = (media ? media.id : null) | ||
this.trigger('fold.media.change', media) | ||
let doc = GridToDoc(content) | ||
@@ -265,0 +286,0 @@ // Cache selection to restore after DOM update |
@@ -228,2 +228,17 @@ import {expect} from 'chai' | ||
it('cancels placeholder', function () { | ||
const ids = ed.insertPlaceholders(1, 1) | ||
ed._placeholderCancel(ids[0]) | ||
expect(ed._foldMedia).to.be.null | ||
const content = ed.editableView.pm.doc.content.content | ||
expect(content.length).to.equal(3) | ||
expect(content[0].textContent).to.equal('Title') | ||
expect(content[0].type.name).to.equal('heading') | ||
expect(content[1].textContent).to.equal('Text 1') | ||
expect(content[1].type.name).to.equal('paragraph') | ||
expect(content[2].textContent).to.equal('Text 2') | ||
expect(content[2].type.name).to.equal('paragraph') | ||
}) | ||
describe('Getting content', function () { | ||
@@ -260,2 +275,14 @@ it('outputs expected content', function () { | ||
}) | ||
it('does not have cancelled placeholder', function () { | ||
const ids = ed.insertPlaceholders(1, 1) | ||
ed._placeholderCancel(ids[0]) | ||
const content = ed.getContent() | ||
const expected = | ||
[ { type: 'h1', html: '<h1>Title</h1>' } | ||
, { type: 'text', html: '<p>Text 1</p>' } | ||
, { type: 'text', html: '<p>Text 2</p>' } | ||
] | ||
expect(content).to.deep.equal(expected) | ||
}) | ||
}) | ||
@@ -262,0 +289,0 @@ }) |
@@ -57,6 +57,7 @@ import {expect} from 'chai' | ||
const widget = PluginWidget.widgets['0000'] | ||
const status = widget.el.querySelector('.Placeholder-status') | ||
expect(widget).to.exist | ||
expect(widget.type).to.equal('placeholder') | ||
expect(widget.el.firstChild.className).to.equal('Placeholder') | ||
expect(widget.el.textContent).to.equal('Status') | ||
expect(status.textContent).to.equal('Status') | ||
}) | ||
@@ -67,4 +68,5 @@ | ||
const widget = PluginWidget.widgets['0000'] | ||
const status = widget.el.querySelector('.Placeholder-status') | ||
expect(widget.type).to.equal('placeholder') | ||
expect(widget.el.textContent).to.equal('Status changed') | ||
expect(status.textContent).to.equal('Status changed') | ||
done() | ||
@@ -83,4 +85,5 @@ }) | ||
const widget = PluginWidget.widgets['0000'] | ||
const status = widget.el.querySelector('.Placeholder-status') | ||
expect(widget.type).to.equal('placeholder') | ||
expect(widget.el.textContent).to.equal('Status changed') | ||
expect(status.textContent).to.equal('Status changed') | ||
done() | ||
@@ -87,0 +90,0 @@ }) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
4543946
21197
136