@the-grid/ed
Advanced tools
Comparing version 0.16.0 to 0.17.0
## dev | ||
## 0.17.0 - 2016-06-21 | ||
* Update to [ProseMirror 0.8.0](https://discuss.prosemirror.net/t/release-0-8-0/336) | ||
* BREAKING -- changed dist directory | ||
* `dist/build.js` for all-inclusive built library that exposes `window.TheGridEd` | ||
* `dist/ed.js` (package main) for the React component | ||
* BREAKING -- commands `link:set` and `link:unset` are replaced with `link:toggle` | ||
## 0.16.0 - 2016-06-14 | ||
@@ -4,0 +12,0 @@ |
@@ -39,7 +39,11 @@ /* eslint no-console: 0 */ | ||
, widgetPath: './node_modules/' | ||
, ref: | ||
function (mounted) { | ||
ed = mounted | ||
console.log(ed) | ||
window.ed = ed | ||
} | ||
} | ||
ed = mountApp(container, props) | ||
console.log(ed) | ||
window.ed = ed | ||
mountApp(container, props) | ||
} | ||
@@ -121,3 +125,3 @@ const initialContent = (window.location.hash === '#fixture' ? fixtureContent : []) | ||
document.getElementById('upload').onclick = function () { | ||
window.ed.pm.signal('ed.menu.file', 1) | ||
ed.execCommand('ed_upload_image') | ||
} | ||
@@ -220,3 +224,3 @@ | ||
} | ||
ed.setContent(json) | ||
setup({initialContent: json, menu}) | ||
} | ||
@@ -223,0 +227,0 @@ document.querySelector('#hydrate').onclick = APIToEditor |
@@ -5,8 +5,9 @@ { | ||
"license": "MIT", | ||
"version": "0.16.0", | ||
"version": "0.17.0", | ||
"description": "the grid api with prosemirror", | ||
"main": "src/ed.js", | ||
"main": "dist/ed.js", | ||
"scripts": { | ||
"start": "export DEV=true; webpack-dev-server --inline --host 0.0.0.0", | ||
"build": "npm run clean; webpack", | ||
"babel": "mkdir -p dist && babel src --out-dir dist", | ||
"build": "npm run clean; webpack; npm run babel", | ||
"builddemo": "npm run clean; export DEMO=true; webpack", | ||
@@ -34,8 +35,8 @@ "clean": "rm -rf dist", | ||
"imgflo-url": "^1.1.0", | ||
"lodash": "^4.12.0", | ||
"prosemirror": "^0.7.0", | ||
"lodash": "^4.13.1", | ||
"prosemirror": "^0.8.2", | ||
"react": "^15.1.0", | ||
"react-dom": "^15.1.0", | ||
"react-tap-event-plugin": "^1.0.0", | ||
"rebass": "^0.2.6", | ||
"rebass": "^0.2.7", | ||
"uuid": "^2.0.2" | ||
@@ -59,4 +60,5 @@ }, | ||
"devDependencies": { | ||
"babel-core": "^6.9.0", | ||
"babel-eslint": "^6.0.4", | ||
"babel-cli": "^6.10.1", | ||
"babel-core": "^6.9.1", | ||
"babel-eslint": "^6.0.5", | ||
"babel-loader": "^6.2.4", | ||
@@ -66,7 +68,7 @@ "babel-preset-es2015": "^6.9.0", | ||
"chai": "^3.5.0", | ||
"copy-webpack-plugin": "^3.0.0", | ||
"eslint": "^2.10.2", | ||
"copy-webpack-plugin": "^3.0.1", | ||
"eslint": "^2.13.1", | ||
"eslint-config-standard": "^5.3.1", | ||
"eslint-plugin-promise": "^1.1.0", | ||
"eslint-plugin-react": "^5.1.1", | ||
"eslint-plugin-promise": "^1.3.2", | ||
"eslint-plugin-react": "^5.2.2", | ||
"eslint-plugin-standard": "^1.3.2", | ||
@@ -78,3 +80,3 @@ "estraverse": "^4.2.0", | ||
"karma": "^0.13.22", | ||
"karma-browserstack-launcher": "^1.0.0", | ||
"karma-browserstack-launcher": "^1.0.1", | ||
"karma-chai": "^0.1.0", | ||
@@ -85,12 +87,12 @@ "karma-chrome-launcher": "^1.0.1", | ||
"karma-mocha": "^1.0.1", | ||
"karma-mocha-reporter": "^2.0.3", | ||
"karma-mocha-reporter": "^2.0.4", | ||
"karma-sourcemap-loader": "^0.3.7", | ||
"karma-webpack": "^1.7.0", | ||
"mocha": "^2.4.5", | ||
"mocha": "^2.5.3", | ||
"mocha-loader": "^0.7.1", | ||
"raw-loader": "^0.5.1", | ||
"style-loader": "^0.13.1", | ||
"webpack": "^1.13.0", | ||
"webpack": "^1.13.1", | ||
"webpack-dev-server": "^1.14.1" | ||
} | ||
} |
@@ -5,7 +5,7 @@ `npm start` | ||
:warning: WIP; not in production yet. :warning: [![Build Status](https://travis-ci.org/the-grid/ed.svg?branch=master)](https://travis-ci.org/the-grid/ed) | ||
[![Build Status](https://travis-ci.org/the-grid/ed.svg?branch=master)](https://travis-ci.org/the-grid/ed) | ||
Using [ProseMirror](http://prosemirror.net/) with data from [the Grid API](http://developer.thegrid.io/) | ||
Demo: [the-grid.github.io/ed/](https://the-grid.github.io/ed/) | ||
Demo: [the-grid.github.io/ed/](https://the-grid.github.io/ed/), [with fixture](https://the-grid.github.io/ed/#fixture) | ||
@@ -16,3 +16,3 @@ The demo shows translating from ProseMirror to the the Grid API JSON and back. | ||
ProseMirror provides a high-level schema-based interface for interacting with `contenteditable`, taking care of that pain. This project is focused on: | ||
ProseMirror provides a high-level schema-based interface for interacting with `contenteditable`, taking care of that pain. Ed is focused on: | ||
@@ -22,17 +22,8 @@ * Schema to translate between the Grid API data and ProseMirror doc type | ||
## todo | ||
# use | ||
* [x] test crucial parts | ||
* [x] iframe widgets | ||
* [x] native widgets | ||
* [x] handle image, video, article, quote types | ||
* [x] edit attribution | ||
* [x] Integrate into web app | ||
* [x] Integrate into mobile apps | ||
* [x] Remove / change cover image | ||
## Using as a React ⚛ component | ||
# use | ||
Ed exposes (a React component)[./src/components/app.js] by default. | ||
Ed exposes a React component by default. | ||
``` jsx | ||
@@ -44,3 +35,3 @@ import Ed from '@the-grid/ed' | ||
return ( | ||
<Ed initialContent={...} onChange={...} ... /> | ||
<Ed key='item-uuid' initialContent={...} onChange={...} ... /> | ||
) | ||
@@ -51,9 +42,16 @@ } | ||
There are also `{mountApp, unmountApp}` helper methods | ||
## Using as a stand-alone library in iframe or similar | ||
Including `dist/build.js` in your page exposes `window.TheGridEd` | ||
``` html | ||
<script src='dist/build.js'></script> | ||
``` | ||
There are `{mountApp, unmountApp}` helper methods | ||
available to use like this: | ||
``` js | ||
import {mountApp, unmountApp} from '@the-grid/ed' | ||
ed = mountApp(document.querySelector('#ed'), { | ||
var container = document.querySelector('#ed') | ||
var ed = window.TheGridEd.mountApp(container, { | ||
// REQUIRED -- Content array from post | ||
@@ -118,3 +116,3 @@ initialContent: [], | ||
}, | ||
// OPTIONAL -- where iframe widgets live relative to app | ||
// OPTIONAL -- where iframe widgets live relative to app (or absolute) | ||
widgetPath: './node_modules/' | ||
@@ -162,4 +160,3 @@ }) | ||
em:toggle | ||
link:unset | ||
link:set | ||
link:toggle | ||
paragraph:make | ||
@@ -166,0 +163,0 @@ heading:make1 |
@@ -11,2 +11,3 @@ require('./app.css') | ||
import EdStore from '../store/ed-store' | ||
import {edCommands} from '../menu/ed-menu' | ||
@@ -132,3 +133,7 @@ | ||
execCommand (commandName) { | ||
this._store.execCommand(commandName) | ||
const item = edCommands[commandName] | ||
if (!item) { | ||
throw new Error('commandName not found') | ||
} | ||
item.spec.run(this._store.pm) | ||
} | ||
@@ -164,3 +169,3 @@ insertPlaceholders (index, count) { | ||
, onShareUrl: React.PropTypes.func.isRequired | ||
, onDropFiles: React.PropTypes.func.isRequired | ||
, onDropFiles: React.PropTypes.func | ||
, onCommandsChanged: React.PropTypes.func | ||
@@ -167,0 +172,0 @@ , onRequestCoverUpload: React.PropTypes.func.isRequired |
@@ -5,25 +5,22 @@ require('./editable.css') | ||
import React, {createElement as el} from 'react' | ||
import {ProseMirror} from 'prosemirror/src/edit/main' | ||
import 'prosemirror/src/inputrules/autoinput' | ||
import 'prosemirror/src/menu/tooltipmenu' | ||
import 'prosemirror/src/menu/menubar' | ||
import {ProseMirror} from 'prosemirror/dist/edit/main' | ||
import {Plugin} from 'prosemirror/dist/edit/plugin' | ||
import {menuBar as pluginMenuBar, tooltipMenu as pluginMenuTip} from 'prosemirror/dist/menu' | ||
import {edBlockMenu, edInlineMenu, edBarMenu} from '../menu/ed-menu' | ||
import GridToDoc from '../convert/grid-to-doc' | ||
import commands from '../commands/index' | ||
import {inlineMenu, blockMenu, barMenu} from '../menu/ed-menu' | ||
import EdKeymap from '../inputrules/ed-keymap' | ||
import EdSchemaFull from '../schema/ed-schema-full' | ||
import EdInputRules from '../inputrules/ed-input-rules' | ||
import {posToIndex} from '../util/pm' | ||
import '../inputrules/autoinput.js' | ||
import PluginWidget from '../plugins/widget.js' | ||
import ShareUrl from '../plugins/share-url' | ||
import FixedMenuBarHack from '../plugins/fixed-menu-hack' | ||
import CommandsInterface from '../plugins/commands-interface' | ||
import PluginShareUrl from '../plugins/share-url' | ||
import PluginContentHints from '../plugins/content-hints' | ||
import PluginPlaceholder from '../plugins/placeholder' | ||
import PluginContentHints from '../plugins/content-hints' | ||
import PluginFixedMenuHack from '../plugins/fixed-menu-hack' | ||
import PluginCommandsInterface from '../plugins/commands-interface' | ||
function noop () { /* noop */ } | ||
class Editable extends React.Component { | ||
@@ -52,3 +49,2 @@ constructor (props) { | ||
, onChange | ||
, onShareFile | ||
, onCommandsChanged | ||
@@ -62,45 +58,36 @@ , widgetPath } = this.props | ||
, autoInput: true | ||
, commands: commands | ||
// , commands: commands | ||
, doc: GridToDoc(initialContent) | ||
, schema: EdSchemaFull | ||
, plugins: [ EdInputRules ] | ||
} | ||
this.pm = new ProseMirror(pmOptions) | ||
let edPluginClasses = | ||
[ PluginWidget | ||
, PluginShareUrl | ||
, PluginContentHints | ||
, PluginPlaceholder | ||
] | ||
if (menuBar) { | ||
this.pm.setOption('menuBar' | ||
, { content: barMenu } | ||
let menu = pluginMenuBar.config( | ||
{ float: false | ||
, content: edBarMenu | ||
} | ||
) | ||
pmOptions.plugins.push(menu) | ||
edPluginClasses.push(PluginFixedMenuHack) | ||
} | ||
if (menuTip) { | ||
this.pm.setOption('tooltipMenu' | ||
, { showLinks: true | ||
, emptyBlockMenu: true | ||
let menu = pluginMenuTip.config( | ||
{ showLinks: true | ||
, selectedBlockMenu: true | ||
, inlineContent: inlineMenu | ||
, selectedBlockContent: inlineMenu | ||
, blockContent: blockMenu | ||
, inlineContent: edInlineMenu | ||
, blockContent: edBlockMenu | ||
, selectedBlockContent: edBlockMenu | ||
} | ||
) | ||
pmOptions.plugins.push(menu) | ||
} | ||
this.pm.on('change', () => { | ||
onChange('EDITABLE_CHANGE', this.pm) | ||
}) | ||
this.pm.on('drop', this.boundOnDrop) | ||
// Setup plugins | ||
let pluginsToInit = | ||
[ PluginWidget | ||
, ShareUrl | ||
, PluginPlaceholder | ||
, PluginContentHints | ||
] | ||
if (menuBar) { | ||
pluginsToInit.push(FixedMenuBarHack) | ||
} | ||
this.pm.on('ed.menu.file', (onShareFile || noop)) | ||
if (onCommandsChanged) { | ||
pluginsToInit.push(CommandsInterface) | ||
edPluginClasses.push(PluginCommandsInterface) | ||
} | ||
@@ -111,3 +98,2 @@ | ||
, editableView: this | ||
, pm: this.pm | ||
, container: plugins | ||
@@ -117,24 +103,30 @@ , widgetPath | ||
this.plugins = pluginsToInit.map((Plugin) => new Plugin(pluginOptions)) | ||
edPluginClasses.forEach(function (plugin) { | ||
const p = new Plugin(plugin, pluginOptions) | ||
pmOptions.plugins.push(p) | ||
}) | ||
this.pm = new ProseMirror(pmOptions) | ||
this.pm.ed = store | ||
this.pm.on.change.add(() => { | ||
onChange('EDITABLE_CHANGE', this.pm) | ||
}) | ||
this.pm.on.domDrop.add(this.boundOnDrop) | ||
this.pm.addKeymap(EdKeymap) | ||
// this.plugins = pluginsToInit.map((Plugin) => new Plugin(pluginOptions)) | ||
onChange('EDITABLE_INITIALIZE', this) | ||
} | ||
componentWillUnmount () { | ||
this.pm.off('change') | ||
this.pm.off('ed.plugin.url') | ||
this.pm.off('ed.menu.file') | ||
this.pm.off('drop', this.boundOnDrop) | ||
this.plugins.forEach((plugin) => plugin.teardown()) | ||
// this.pm.off('change') | ||
// this.pm.off('ed.plugin.url') | ||
// this.pm.off('ed.menu.file') | ||
// this.pm.off('drop', this.boundOnDrop) | ||
// this.plugins.forEach((plugin) => plugin.teardown()) | ||
} | ||
updatePlaceholderHeights (changes) { | ||
// Do this in a batch, with one widget remeasure/move | ||
for (let i = 0, len = changes.length; i < len; i++) { | ||
const change = changes[i] | ||
// TODO do this with standard pm.tr interface, not direct DOM | ||
if (!this.refs.mirror) return | ||
const placeholder = this.refs.mirror.querySelector(`.EdSchemaMedia[grid-id="${change.id}"]`) | ||
placeholder.style.height = change.height + 'px' | ||
} | ||
this.pm.signal('draw') | ||
} | ||
onDrop (event) { | ||
@@ -141,0 +133,0 @@ if (!event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length) return |
@@ -1,2 +0,1 @@ | ||
import {toDOM} from 'prosemirror/src/format' | ||
import {isMediaType} from './types' | ||
@@ -7,5 +6,6 @@ import BlockMetaSchema from '../schema/block-meta' | ||
export default function (doc, apiContentMap) { | ||
const fragment = toDOM(doc) | ||
const fragment = doc.content.toDOM() | ||
const dom = document.createElement('div') | ||
dom.appendChild(fragment) | ||
let currentContent = [] | ||
@@ -12,0 +12,0 @@ let starred = true |
@@ -1,10 +0,11 @@ | ||
import {fromDOM} from 'prosemirror/src/format' | ||
import EdSchema from '../schema/ed-schema-full' | ||
import {isMediaType, isHTMLType} from './types' | ||
import EdSchemaFull from '../schema/ed-schema-full' | ||
// import EdSchemaFull from '../schema/ed-schema-full' | ||
import determineFold from './determine-fold' | ||
import spaceContent from './space-content' | ||
import IframeInfo from '../plugins/iframe-info' | ||
export default function (items, schema = EdSchemaFull) { | ||
export default function (items) { | ||
const container = document.createElement('div') | ||
@@ -26,3 +27,3 @@ let {starred, unstarred} = determineFold(items) | ||
} | ||
return fromDOM(schema, container) | ||
return EdSchema.parseDOM(container) | ||
} | ||
@@ -42,2 +43,6 @@ | ||
el.setAttribute('grid-type', type) | ||
const iframe = IframeInfo[type] | ||
if (iframe) { | ||
el.setAttribute('grid-initial-height', iframe.initialHeight) | ||
} | ||
} else { | ||
@@ -44,0 +49,0 @@ return null |
@@ -10,2 +10,3 @@ import App from './components/app' | ||
export function mountApp (container, props) { | ||
let mounted | ||
if (!container) { | ||
@@ -17,5 +18,11 @@ throw new Error('Missing container') | ||
} | ||
if (!props.ref) { | ||
// HACK might be async in future React | ||
props.ref = function (m) { | ||
mounted = m | ||
} | ||
} | ||
// Setup main DOM structure | ||
const app = React.createElement(App, props) | ||
const mounted = ReactDOM.render(app, container) | ||
ReactDOM.render(app, container) | ||
return mounted | ||
@@ -22,0 +29,0 @@ } |
@@ -1,36 +0,64 @@ | ||
import {MenuCommandGroup, Dropdown | ||
, inlineGroup, blockGroup | ||
, historyGroup} from 'prosemirror/src/menu/menu' | ||
import {Dropdown, undoItem, redoItem, liftItem} from 'prosemirror/dist/menu/menu' | ||
import {buildMenuItems} from 'prosemirror/dist/example-setup' | ||
import EdSchema from '../schema/ed-schema-full' | ||
import menuImage from './menu-image' | ||
const textblockMenu = new Dropdown( | ||
{ label: 'Type...' | ||
, activeLabel: true | ||
, class: 'ProseMirror-textblock-dropdown' | ||
const menuItems = buildMenuItems(EdSchema) | ||
const { makeParagraph | ||
, makeHead1 | ||
, makeHead2 | ||
, makeHead3 | ||
, wrapBlockQuote | ||
, wrapBulletList | ||
, wrapOrderedList | ||
, toggleEm | ||
, toggleLink | ||
, toggleStrong | ||
} = menuItems | ||
export const edCommands = | ||
{ 'strong:toggle': toggleStrong | ||
, 'em:toggle': toggleEm | ||
, 'link:toggle': toggleLink | ||
, 'paragraph:make': makeParagraph | ||
, 'heading:make1': makeHead1 | ||
, 'heading:make2': makeHead2 | ||
, 'heading:make3': makeHead3 | ||
, 'bullet_list:wrap': wrapBulletList | ||
, 'ordered_list:wrap': wrapOrderedList | ||
, 'blockquote:wrap': wrapBlockQuote | ||
, 'lift': liftItem | ||
, 'ed_upload_image': menuImage | ||
, 'undo': undoItem | ||
, 'redo': redoItem | ||
} | ||
, | ||
[ new MenuCommandGroup('textblock') | ||
, new MenuCommandGroup('textblockHeading') | ||
const typeDropdown = new Dropdown( | ||
[ makeParagraph | ||
, makeHead1 | ||
, makeHead2 | ||
, makeHead3 | ||
] | ||
, {label: 'Type...'} | ||
) | ||
const edMenuGroup = new MenuCommandGroup('ed_block') | ||
export const inlineMenu = | ||
[ inlineGroup | ||
, textblockMenu | ||
, blockGroup | ||
export const edBlockMenu = | ||
[ [ typeDropdown ] | ||
, [ wrapBulletList | ||
, wrapOrderedList | ||
, wrapBlockQuote | ||
, liftItem | ||
] | ||
, [ menuImage ] | ||
] | ||
export const blockMenu = | ||
[ textblockMenu | ||
, blockGroup | ||
, edMenuGroup | ||
export const edInlineMenu = | ||
[ [ toggleEm | ||
, toggleLink | ||
, toggleStrong | ||
] | ||
] | ||
export const barMenu = | ||
[ inlineGroup | ||
, textblockMenu | ||
, blockGroup | ||
, edMenuGroup | ||
, historyGroup | ||
] | ||
export const edBarMenu = edInlineMenu | ||
.concat(edBlockMenu) | ||
.concat([[undoItem, redoItem]]) |
@@ -1,17 +0,16 @@ | ||
import {UpdateScheduler} from 'prosemirror/src/ui/update' | ||
import {resolveGroup, Dropdown} from 'prosemirror/src/menu/menu' | ||
import {barMenu} from '../menu/ed-menu' | ||
import {edCommands} from '../menu/ed-menu' | ||
export default class CommandsInterface { | ||
constructor (options) { | ||
const {ed, pm} = options | ||
constructor (pm, options) { | ||
const {ed} = options | ||
if (!ed.onCommandsChanged) { | ||
throw new Error('Should not init this plugin without Ed onCommandsChanged option.') | ||
} | ||
this.pm = pm | ||
this.ed = ed | ||
this.pm = pm | ||
// Schedule updates for available commands | ||
this.updater = new UpdateScheduler(pm | ||
, 'selectionChange blur focus commandsChanged' | ||
this.updater = pm.updateScheduler( | ||
[ pm.on.selectionChange | ||
] | ||
, this.update.bind(this) | ||
@@ -27,19 +26,7 @@ ) | ||
for (let i = 0; i < barMenu.length; i++) { | ||
const items = resolveGroup(this.pm, barMenu[i]) | ||
for (let j = 0; j < items.length; j++) { | ||
const item = items[j] | ||
if (item instanceof Dropdown) { | ||
for (let k = 0, len = item.content.length; k < len; k++) { | ||
const dropdownItems = resolveGroup(this.pm, item.content[k]) | ||
for (let l = 0, len = dropdownItems.length; l < len; l++) { | ||
commands[dropdownItems[l].commandName] = this.makeCommand(dropdownItems[l]) | ||
} | ||
} | ||
continue | ||
} | ||
commands[items[j].commandName] = this.makeCommand(items[j]) | ||
} | ||
let keys = Object.keys(edCommands) | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i] | ||
const item = edCommands[key] | ||
commands[key] = this.makeCommand(item) | ||
} | ||
@@ -51,3 +38,3 @@ | ||
let state = 'inactive' | ||
let command = item.command(this.pm) | ||
let command = item.spec | ||
if (command.active && command.active(this.pm)) { | ||
@@ -54,0 +41,0 @@ state = 'active' |
@@ -5,53 +5,48 @@ /* | ||
import {UpdateScheduler} from 'prosemirror/src/ui/update' | ||
import _ from '../util/lodash' | ||
// Functions to bind in class constructor | ||
// Should use debounced version | ||
function onDocChanged () { | ||
const doc = this.pm.doc | ||
let hasCover = false | ||
let hasFold = false | ||
for (let i = 0, len = doc.childCount; i < len; i++) { | ||
const node = doc.child(i) | ||
const {name} = node.type | ||
if (name === 'media') { | ||
const {type} = node.attrs | ||
if (type === 'image' || type === 'placeholder') { | ||
hasCover = true | ||
} | ||
} | ||
if (name === 'horizontal_rule') { | ||
hasFold = true | ||
} | ||
} | ||
this.ed.trigger('plugin.contenthints', {hasCover, hasFold}) | ||
// Signal widgets initialized if first | ||
if (!this.initialized) { | ||
this.initialized = true | ||
this.ed.trigger('plugin.contenthints.initialized') | ||
} | ||
} | ||
// The plugin | ||
export default class PluginContentHints { | ||
constructor (options) { | ||
this.onDocChanged = onDocChanged.bind(this) | ||
this.debouncedDocChanged = _.debounce(this.onDocChanged, 500) | ||
constructor (pm, options) { | ||
this.boundOnDocChanged = this.onDocChanged.bind(this) | ||
this.debouncedDocChanged = _.debounce(this.boundOnDocChanged, 500) | ||
this.pm = pm | ||
this.ed = options.ed | ||
this.pm = options.pm | ||
window.addEventListener('resize', this.debouncedDocChanged) | ||
this.updater = new UpdateScheduler(this.pm, 'change', this.debouncedDocChanged) | ||
this.updater = pm.updateScheduler([pm.on.change], this.debouncedDocChanged) | ||
this.updater.force() | ||
} | ||
teardown () { | ||
detach () { | ||
this.updater.detach() | ||
window.removeEventListener('resize', this.debouncedDocChanged) | ||
} | ||
onDocChanged () { | ||
// Should use debounced version | ||
const doc = this.pm.doc | ||
let hasCover = false | ||
let hasFold = false | ||
doc.forEach(function (node, offset, index) { | ||
const {name} = node.type | ||
if (name === 'media') { | ||
const {type} = node.attrs | ||
if (type === 'image' || type === 'placeholder') { | ||
hasCover = true | ||
} | ||
} | ||
if (name === 'horizontal_rule') { | ||
hasFold = true | ||
} | ||
}) | ||
this.ed.trigger('plugin.contenthints', {hasCover, hasFold}) | ||
// Signal widgets initialized if first | ||
if (!this.initialized) { | ||
this.initialized = true | ||
this.ed.trigger('plugin.contenthints.initialized') | ||
} | ||
} | ||
} |
@@ -1,2 +0,1 @@ | ||
import {UpdateScheduler} from 'prosemirror/src/ui/update' | ||
import _ from '../util/lodash' | ||
@@ -10,2 +9,3 @@ | ||
} | ||
this.menuEl.style.position = 'absolute' | ||
this.menuEl.style.top = (0 - contentTop) + 'px' | ||
@@ -15,12 +15,13 @@ } | ||
function spaceContent () { | ||
const menuHeight = this.menuEl.style.minHeight | ||
this.menuEl.style.minHeight = 'inherit' | ||
const menuHeight = this.menuEl.offsetHeight | ||
if (this.menuHeight !== menuHeight) { | ||
this.menuHeight = menuHeight | ||
this.contentEl.style.paddingTop = menuHeight | ||
this.contentEl.style.paddingTop = (menuHeight + 36) + 'px' | ||
} | ||
} | ||
export default class FixedMenuBarHack { | ||
constructor (options) { | ||
const {pm} = options | ||
constructor (pm, options) { | ||
this.pm = pm | ||
@@ -36,3 +37,4 @@ | ||
this.spaceContent = _.debounce(spaceContent, 250).bind(this) | ||
this.updater = new UpdateScheduler(pm, 'selectionChange activeMarkChange commandsChanged', this.spaceContent) | ||
const {selectionChange} = pm.on | ||
this.updater = pm.updateScheduler([selectionChange], this.spaceContent) | ||
this.updater.force() | ||
@@ -44,12 +46,12 @@ | ||
window.addEventListener('scroll', this.onScroll) | ||
window.addEventListener('resize', this.spaceContent) | ||
} | ||
teardown () { | ||
detach () { | ||
if (!this.menuEl) { | ||
return | ||
} | ||
this.menuEl.style.position = 'fixed' | ||
this.menuEl.style.top = '0px' | ||
window.removeEventListener('scroll', this.onScroll) | ||
window.removeEventListener('resize', this.spaceContent) | ||
this.updater.detach() | ||
} | ||
} |
@@ -5,50 +5,44 @@ /* | ||
import {UpdateScheduler} from 'prosemirror/src/ui/update' | ||
import _ from '../util/lodash' | ||
// Functions to bind in class constructor | ||
// Should use debounced version | ||
function onDOMChanged () { | ||
const els = this.pm.content.children | ||
for (let i = 0, len = els.length; i < len; i++) { | ||
const el = els[i] | ||
const tag = el.tagName | ||
if (tag === 'H1' || tag === 'P') { | ||
if (el.textContent === '') { | ||
el.classList.add('empty') | ||
} else { | ||
el.classList.remove('empty') | ||
} | ||
} | ||
} | ||
// Signal widgets initialized if first | ||
if (!this.initialized) { | ||
this.initialized = true | ||
this.ed.trigger('plugin.placeholder.initialized') | ||
} | ||
} | ||
// The plugin | ||
export default class PluginPlaceholder { | ||
constructor (options) { | ||
this.onDOMChanged = onDOMChanged.bind(this) | ||
this.debouncedDOMChanged = _.debounce(this.onDOMChanged, 50) | ||
constructor (pm, options) { | ||
this.boundOnDOMChanged = this.onDOMChanged.bind(this) | ||
this.debouncedDOMChanged = _.debounce(this.boundOnDOMChanged, 50) | ||
this.pm = pm | ||
this.ed = options.ed | ||
this.pm = options.pm | ||
window.addEventListener('resize', this.debouncedDOMChanged) | ||
this.updater = new UpdateScheduler(this.pm, 'change draw flush', this.debouncedDOMChanged) | ||
this.updater = pm.updateScheduler([pm.on.change], this.debouncedDOMChanged) | ||
this.pm.content.addEventListener('compositionstart', this.debouncedDOMChanged) | ||
this.updater.force() | ||
} | ||
teardown () { | ||
detach () { | ||
this.updater.detach() | ||
this.pm.content.removeEventListener('compositionstart', this.debouncedDOMChanged) | ||
window.removeEventListener('resize', this.debouncedDOMChanged) | ||
} | ||
onDOMChanged () { | ||
// Should use debounced version | ||
const els = this.pm.content.children | ||
for (let i = 0, len = els.length; i < len; i++) { | ||
const el = els[i] | ||
const tag = el.tagName | ||
if (tag === 'H1' || tag === 'P') { | ||
if (el.textContent === '') { | ||
el.classList.add('empty') | ||
} else { | ||
el.classList.remove('empty') | ||
} | ||
} | ||
} | ||
// Signal widgets initialized if first | ||
if (!this.initialized) { | ||
this.initialized = true | ||
this.ed.trigger('plugin.placeholder.initialized') | ||
} | ||
} | ||
} |
import uuid from 'uuid' | ||
import {isUrl} from '../util/url' | ||
function testPrevUrl () { | ||
// Entered into a new block, collapsed selection | ||
const selection = this.ed.pm.selection | ||
if (!selection.empty) return | ||
// Current position (under potential url line) | ||
const currentNode = this.ed.pm.doc.childBefore(selection.anchor) | ||
if (!currentNode || currentNode.index < 1) return | ||
export default class ShareUrl { | ||
constructor (pm, options) { | ||
this.boundTestPrevUrl = this.testPrevUrl.bind(this) | ||
const {ed} = options | ||
this.ed = ed | ||
this.pm = pm | ||
this.pm.on.change.add(this.boundTestPrevUrl) | ||
} | ||
detach () { | ||
this.pm.on.change.remove(this.boundTestPrevUrl) | ||
} | ||
testPrevUrl () { | ||
// Entered into a new block, collapsed selection | ||
const selection = this.ed.pm.selection | ||
if (!selection.empty) return | ||
// Potential url line | ||
const index = currentNode.index - 1 | ||
const prevNode = this.ed.pm.doc.maybeChild(index) | ||
if (!prevNode || prevNode.type.name !== 'paragraph') return | ||
// Current position (under potential url line) | ||
const currentNode = this.ed.pm.doc.childBefore(selection.anchor) | ||
if (!currentNode || currentNode.index < 1) return | ||
// Test if url | ||
const url = prevNode.textContent.trim() | ||
if (!url || !isUrl(url)) return | ||
// Potential url line | ||
const index = currentNode.index - 1 | ||
const prevNode = this.ed.pm.doc.maybeChild(index) | ||
if (!prevNode || prevNode.type.name !== 'paragraph') return | ||
// Make share | ||
const id = uuid.v4() | ||
const block = | ||
{ id | ||
, type: 'placeholder' | ||
, metadata: | ||
{ status: `Sharing... ${url}` | ||
, percent: 0 | ||
// Test if url | ||
const url = prevNode.textContent.trim() | ||
if (!url || !isUrl(url)) return | ||
// Make share | ||
const id = uuid.v4() | ||
const block = | ||
{ id | ||
, type: 'placeholder' | ||
, metadata: | ||
{ status: `Sharing... ${url}` | ||
, percent: 0 | ||
} | ||
} | ||
} | ||
this.ed.routeChange('PLUGIN_URL', {index, id, block, url}) | ||
} | ||
export default class ShareUrl { | ||
constructor (options) { | ||
this.testPrevUrl = testPrevUrl.bind(this) | ||
const {pm, ed} = options | ||
this.ed = ed | ||
this.pm = pm | ||
this.pm.on('change', this.testPrevUrl) | ||
this.ed.routeChange('PLUGIN_URL', {index, id, block, url}) | ||
} | ||
teardown () { | ||
this.pm.off('change', this.testPrevUrl) | ||
} | ||
} |
@@ -12,3 +12,3 @@ export default class WidgetBase { | ||
this.width = 1 | ||
this.height = 50 | ||
this.height = 0 | ||
@@ -15,0 +15,0 @@ // Base div container |
import WidgetBase from './widget-base' | ||
import IframeInfo from './iframe-info' | ||
const Widgets = | ||
{ code: | ||
{ src: '@the-grid/ced/editor/index.html' | ||
, initialHeight: 50 | ||
} | ||
, location: | ||
{ src: '@the-grid/ed-location/index.html' | ||
, initialHeight: 320 | ||
} | ||
} | ||
function postInitialBlock () { | ||
@@ -26,2 +15,3 @@ this.postMessage('setblock', this.initialBlock) | ||
export default class WidgetIframe extends WidgetBase { | ||
@@ -33,3 +23,3 @@ static type () { return 'iframe -- extend me' } | ||
const widget = Widgets[options.type] | ||
const widget = IframeInfo[options.type] | ||
if (!widget) { | ||
@@ -36,0 +26,0 @@ throw new Error('No iframe widget of that type') |
@@ -8,3 +8,2 @@ /* | ||
import _ from '../util/lodash' | ||
import {UpdateScheduler} from 'prosemirror/src/ui/update' | ||
@@ -24,131 +23,2 @@ // WidgetTypes keys correspond with PM media block's grid-type attribute | ||
// Should use debounced version | ||
function onDOMChanged () { | ||
// Mount or move widget overlays | ||
const els = this.pm.content.children | ||
let inDoc = [] | ||
let heightChanges = [] | ||
let idDuplicates = [] | ||
for (let i = 0, len = els.length; i < len; i++) { | ||
const el = els[i] | ||
const id = el.getAttribute('grid-id') | ||
if (!id) { | ||
// Not a media block | ||
continue | ||
} | ||
const type = el.getAttribute('grid-type') | ||
if (!type) { | ||
throw new Error('Bad placeholder!') | ||
} | ||
if (inDoc.indexOf(id) !== -1) { | ||
idDuplicates.push(id) | ||
} | ||
inDoc.push(id) | ||
const rectangle = | ||
{ top: el.offsetTop | ||
, left: el.offsetLeft | ||
, width: el.offsetWidth | ||
, height: el.offsetHeight | ||
} | ||
// HACK paste iframe, queue cached height for placeholder | ||
if (rectangle.height === 0 && this.widgets[id] && this.widgets[id].height) { | ||
heightChanges.push( | ||
{ id: id | ||
, height: this.widgets[id].height | ||
} | ||
) | ||
} | ||
const initialFocus = (el.getAttribute('grid-initial-focus') === 'true') | ||
this.checkWidget(id, type, rectangle, initialFocus) | ||
} | ||
// Hide or show widgets | ||
let inDOM = Object.keys(this.widgets) | ||
for (let i = 0, len = inDOM.length; i < len; i++) { | ||
const id = inDOM[i] | ||
const widget = this.widgets[id] | ||
if (inDoc.indexOf(id) !== -1) { | ||
widget.show() | ||
} else { | ||
widget.hide() | ||
} | ||
} | ||
// Measure inner heights of widgets | ||
for (let i = 0, len = inDOM.length; i < len; i++) { | ||
const id = inDOM[i] | ||
const widget = this.widgets[id] | ||
if (!widget.shown) continue | ||
const innerHeight = widget.getHeight() | ||
if (innerHeight !== widget.height) { | ||
heightChanges.push( | ||
{ id: id | ||
, height: innerHeight | ||
} | ||
) | ||
} | ||
} | ||
if (heightChanges.length) { | ||
// Will trigger a redraw / this onDOMChanged again | ||
this.editableView.updatePlaceholderHeights(heightChanges) | ||
} | ||
// Copy & pasted | ||
if (idDuplicates.length) { | ||
this.ed.routeChange('DEDUPE_IDS') | ||
return | ||
} | ||
// Signal widgets initialized if first | ||
if (!this.initialized) { | ||
this.initialized = true | ||
this.ed.trigger('plugin.widget.initialized') | ||
} | ||
} | ||
function checkWidget (id, type, rectangle, initialFocus) { | ||
let widget = this.widgets[id] | ||
if (widget && widget.type !== type) { | ||
// Remove it | ||
widget.teardown() | ||
// Will be overwritten in initializeWidget | ||
widget = null | ||
} | ||
if (widget) { | ||
// Move it | ||
widget.move(rectangle) | ||
} else { | ||
// Make it | ||
this.initializeWidget(id, type, rectangle, initialFocus) | ||
} | ||
} | ||
function initializeWidget (id, type, rectangle, initialFocus) { | ||
let Widget = WidgetTypes[type] || WidgetTypes.react | ||
let initialBlock = this.ed.getBlock(id) | ||
if (!initialBlock) { | ||
initialBlock = this.initializeBlock(id, type) | ||
} | ||
if (!initialBlock) { | ||
throw new Error('Block does not exist in content') | ||
} | ||
this.widgets[id] = new Widget( | ||
{ ed: this.ed | ||
, id | ||
, type | ||
, initialBlock | ||
, widgetContainer: this.el | ||
, initialRectangle: rectangle | ||
, initialFocus | ||
, widgetPath: this.widgetPath | ||
} | ||
) | ||
this.pm.signal('ed.plugin.widget.one.initialized', id) | ||
} | ||
function initializeBlock (id, type) { | ||
@@ -179,8 +49,11 @@ const block = | ||
case 'height': | ||
if (isNaN(message.data.payload)) throw new Error('Iframe height message with non-numeric payload') | ||
this.editableView.updatePlaceholderHeights([ | ||
if (isNaN(message.data.payload)) { | ||
throw new Error('Iframe height message with non-numeric payload') | ||
} | ||
this.scheduledHeightUpdates.push( | ||
{ id: message.data.id | ||
, height: message.data.payload | ||
} | ||
]) | ||
) | ||
this.triggerUpdate() | ||
break | ||
@@ -197,9 +70,8 @@ case 'cursor': | ||
export default class PluginWidget { | ||
constructor (options) { | ||
this.onDOMChanged = onDOMChanged.bind(this) | ||
this.debouncedDOMChanged = _.debounce(this.onDOMChanged, 50) | ||
this.checkWidget = checkWidget.bind(this) | ||
this.initializeWidget = initializeWidget.bind(this) | ||
constructor (pm, options) { | ||
this.onIframeMessage = onIframeMessage.bind(this) | ||
this.initializeBlock = initializeBlock.bind(this) | ||
this.boundReadDOM = this.readDOM.bind(this) | ||
this.boundTriggerUpdate = this.triggerUpdate.bind(this) | ||
this.debouncedTriggerUpdate = _.debounce(this.boundTriggerUpdate, 50) | ||
@@ -210,3 +82,3 @@ this.initialized = false | ||
this.editableView = options.editableView | ||
this.pm = options.pm | ||
this.pm = pm | ||
this.widgetPath = options.widgetPath | ||
@@ -218,13 +90,17 @@ | ||
options.container.appendChild(this.el) | ||
this.scheduledHeightUpdates = [] | ||
this.updater = new UpdateScheduler(this.pm, 'draw flush', this.debouncedDOMChanged) | ||
// Seems to work better to read first, despite docs: | ||
// http://prosemirror.net/version/0.8.0.html#ProseMirror.scheduleDOMUpdate | ||
const {draw} = this.pm.on | ||
this.updater = this.pm.updateScheduler([draw], this.boundReadDOM) | ||
this.updater.force() | ||
this.interval = window.setInterval(this.debouncedDOMChanged, 1000) | ||
window.addEventListener('resize', this.debouncedDOMChanged) | ||
this.interval = window.setInterval(this.debouncedTriggerUpdate, 1000) | ||
window.addEventListener('resize', this.debouncedTriggerUpdate) | ||
window.addEventListener('message', this.onIframeMessage) | ||
} | ||
teardown () { | ||
detach () { | ||
this.updater.detach() | ||
window.clearInterval(this.interval) | ||
window.removeEventListener('resize', this.debouncedDOMChanged) | ||
window.removeEventListener('resize', this.debouncedTriggerUpdate) | ||
window.removeEventListener('message', this.onIframeMessage) | ||
@@ -234,2 +110,149 @@ | ||
} | ||
triggerUpdate () { | ||
const update = () => this.updater.force() | ||
setTimeout(update, 0) | ||
} | ||
readDOM () { | ||
// Should read DOM, not write | ||
let inDOM = Object.keys(this.widgets) | ||
let inDoc = [] | ||
let toInit = [] | ||
let toMove = [] | ||
let toHide = [] | ||
let toShow = [] | ||
let toUndupe = [] | ||
let toChangeHeight = this.scheduledHeightUpdates | ||
this.scheduledHeightUpdates = [] | ||
const els = this.pm.content.children | ||
for (let i = 0, len = els.length; i < len; i++) { | ||
const el = els[i] | ||
const id = el.getAttribute('grid-id') | ||
if (!id) { | ||
// Not a media block | ||
continue | ||
} | ||
const type = el.getAttribute('grid-type') | ||
if (!type) { | ||
throw new Error('Bad placeholder!') | ||
} | ||
if (inDoc.indexOf(id) !== -1) { | ||
toUndupe.push(id) | ||
} | ||
inDoc.push(id) | ||
const rectangle = | ||
{ top: el.offsetTop | ||
, left: el.offsetLeft | ||
, width: el.offsetWidth | ||
, height: el.offsetHeight | ||
} | ||
const widget = this.widgets[id] | ||
let needsReInit = false | ||
if (widget && widget.type !== type) { | ||
// Need to re-initialize new widget type | ||
widget.teardown() | ||
needsReInit = true | ||
} | ||
// Queue init | ||
if (!widget || needsReInit) { | ||
const initialFocus = (el.getAttribute('grid-initial-focus') === 'true') | ||
toInit.push({id, type, rectangle, initialFocus}) | ||
continue | ||
} | ||
if (!widget.shown) continue | ||
// Queue move | ||
toMove.push({id, rectangle}) | ||
// Queue height | ||
const innerHeight = widget.getHeight() | ||
if (innerHeight !== widget.height) { | ||
toChangeHeight.push( | ||
{ id: id | ||
, height: innerHeight | ||
} | ||
) | ||
} | ||
// HACK paste iframe, queue cached height for placeholder | ||
// if (rectangle.height === 0 && this.widgets[id] && this.widgets[id].height) { | ||
// toChangeHeight.push( | ||
// { id: id | ||
// , height: this.widgets[id].height | ||
// } | ||
// ) | ||
// } | ||
} | ||
// Queue hide or show widgets | ||
for (let i = 0, len = inDOM.length; i < len; i++) { | ||
const id = inDOM[i] | ||
if (inDoc.indexOf(id) !== -1) { | ||
toShow.push(id) | ||
} else { | ||
toHide.push(id) | ||
} | ||
} | ||
return () => this.writeDOM(toInit, toMove, toHide, toShow, toUndupe, toChangeHeight) | ||
} | ||
writeDOM (toInit, toMove, toHide, toShow, toUndupe, toChangeHeight) { | ||
if (toUndupe.length) { | ||
this.ed.routeChange('DEDUPE_IDS') | ||
return | ||
} | ||
toInit.forEach((config) => this.writeWidgetInit(config)) | ||
toMove.forEach((config) => this.writeWidgetMove(config)) | ||
toHide.forEach((config) => this.writeWidgetHide(config)) | ||
toShow.forEach((config) => this.writeWidgetShow(config)) | ||
toChangeHeight.forEach((config) => this.writePlaceholderHeight(config)) | ||
if (!this.initialized) { | ||
this.initialized = true | ||
this.ed.trigger('plugin.widget.initialized', this) | ||
} | ||
if (toInit.length || toChangeHeight.length) { | ||
// Loop to remeasure and move | ||
return this.boundReadDOM | ||
} | ||
} | ||
writeWidgetInit ({id, type, rectangle, initialFocus}) { | ||
let Widget = WidgetTypes[type] || WidgetTypes.react | ||
let initialBlock = this.ed.getBlock(id) | ||
if (!initialBlock) { | ||
initialBlock = this.initializeBlock(id, type) | ||
} | ||
if (!initialBlock) { | ||
throw new Error('Block does not exist in content') | ||
} | ||
this.widgets[id] = new Widget( | ||
{ ed: this.ed | ||
, id | ||
, type | ||
, initialBlock | ||
, widgetContainer: this.el | ||
, initialRectangle: rectangle | ||
, initialFocus | ||
, widgetPath: this.widgetPath | ||
} | ||
) | ||
this.ed.trigger('plugin.widget.one.initialized', id) | ||
} | ||
writeWidgetMove ({id, rectangle}) { | ||
this.widgets[id].move(rectangle) | ||
} | ||
writeWidgetHide (id) { | ||
this.widgets[id].hide() | ||
} | ||
writeWidgetShow (id) { | ||
this.widgets[id].show() | ||
} | ||
writePlaceholderHeight ({id, height}) { | ||
// TODO pm.tr interface | ||
const placeholder = this.pm.content.querySelector(`.EdSchemaMedia[grid-id="${id}"]`) | ||
placeholder.style.height = height + 'px' | ||
} | ||
} |
// See also, PM's default schema: | ||
// https://github.com/ProseMirror/prosemirror/blob/86d55c3f48a0b092bb446fe5c33cc4978c2ba61c/src/model/defaultschema.js#L91-L118 | ||
import {Schema, Doc, | ||
Heading, BlockQuote, Paragraph, | ||
HorizontalRule, | ||
BulletList, OrderedList, ListItem, | ||
Text, | ||
HardBreak, | ||
EmMark, StrongMark, LinkMark} from 'prosemirror/src/model' | ||
import {Schema} from 'prosemirror/dist/model' | ||
import { Doc | ||
, Heading | ||
, BlockQuote | ||
, Paragraph | ||
, HorizontalRule | ||
, BulletList | ||
, OrderedList | ||
, ListItem | ||
, Text | ||
, HardBreak | ||
, EmMark | ||
, StrongMark | ||
, LinkMark } from 'prosemirror/dist/schema-basic' | ||
import {Media} from './media' | ||
class EdHeading extends Heading { | ||
@@ -15,0 +24,0 @@ // Limit h1, h2, h3 |
require('./media.css') | ||
import {Block, Attribute} from 'prosemirror/src/model' | ||
// import {NodeKindTop} from './ed-nodes' | ||
import {Block, Attribute} from 'prosemirror/dist/model' | ||
import {isMediaType} from '../convert/types' | ||
export class Media extends Block { | ||
get isBlock () { return true } | ||
get isInline () { return false } | ||
get isLeaf () { return true } | ||
@@ -15,3 +16,3 @@ get locked () { return true } | ||
, type: new Attribute() | ||
, height: new Attribute({default: 50}) | ||
, initialHeight: new Attribute({default: 72}) | ||
, initialFocus: new Attribute({default: false}) | ||
@@ -21,32 +22,25 @@ } | ||
} | ||
} | ||
Media.register('parseDOM', 'div' | ||
, { tag: 'div' | ||
, rank: 9999 | ||
, parse: function (dom, state) { | ||
const id = dom.getAttribute('grid-id') | ||
const type = dom.getAttribute('grid-type') | ||
if (!id || !type) { | ||
get matchDOMTag () { | ||
return {'div[grid-type]': function (dom) { | ||
const id = dom.getAttribute('grid-id') | ||
const type = dom.getAttribute('grid-type') | ||
const initialHeight = dom.getAttribute('grid-initial-height') | ||
if (id && type && isMediaType(type)) { | ||
return {id, type, initialHeight} | ||
} | ||
return false | ||
} | ||
state.insert(this, {id, type}) | ||
}} | ||
} | ||
toDOM (node) { | ||
const {id, type, initialHeight, initialFocus} = node.attrs | ||
return ['div' | ||
, { 'class': 'EdSchemaMedia' | ||
, 'grid-id': id | ||
, 'grid-type': type | ||
, 'grid-initial-focus': initialFocus | ||
, 'grid-initial-height': initialHeight | ||
, 'style': `height: ${initialHeight}px;` | ||
} | ||
] | ||
} | ||
) | ||
Media.prototype.serializeDOM = (node, s) => { | ||
const {id, type, height, initialFocus} = node.attrs | ||
if (!id) { | ||
throw new Error('Can not serialize Media div without id') | ||
} | ||
if (!type) { | ||
throw new Error('Can not serialize Media div without type') | ||
} | ||
return s.elt('div' | ||
, { class: 'EdSchemaMedia' | ||
, 'grid-id': id | ||
, 'grid-type': type | ||
, 'grid-initial-focus': initialFocus | ||
, style: `height: ${height};` | ||
} | ||
) | ||
} |
@@ -29,3 +29,2 @@ import '../util/react-tap-hack' | ||
this.onShareUrl = options.onShareUrl | ||
this.onShareFile = options.onShareFile | ||
this.onPlaceholderCancel = options.onPlaceholderCancel || noop | ||
@@ -37,2 +36,5 @@ this.onCommandsChanged = options.onCommandsChanged | ||
this.onShareFile = options.onShareFile || noop | ||
this.on('command.menu.file', this.onShareFile) | ||
// Listen for first render | ||
@@ -200,5 +202,2 @@ this.on('plugin.widget.initialized', options.onMount || noop) | ||
.apply() | ||
// Trigger event for widget system | ||
setTimeout(() => this.pm.signal('draw'), 0) | ||
} | ||
@@ -222,4 +221,2 @@ _dedupeIds () { | ||
} | ||
// Trigger event for widget system | ||
setTimeout(() => this.pm.signal('draw'), 0) | ||
} | ||
@@ -253,5 +250,2 @@ getBlock (id) { | ||
.apply() | ||
// Trigger event for widget system | ||
setTimeout(() => this.pm.signal('draw'), 0) | ||
} | ||
@@ -276,5 +270,2 @@ _insertBlocks (index, blocks) { | ||
} | ||
// Trigger event for widget system | ||
setTimeout(() => this.pm.signal('draw'), 0) | ||
} | ||
@@ -383,4 +374,2 @@ insertPlaceholders (index, count) { | ||
// Trigger event for widget system | ||
setTimeout(() => this.pm.signal('draw'), 0) | ||
// Focus first textblock | ||
@@ -395,5 +384,3 @@ try { | ||
getContent () { | ||
const doc = this.pm.getContent() | ||
const content = DocToGrid(doc, this._content) | ||
return content | ||
return DocToGrid(this.pm.doc, this._content) | ||
} | ||
@@ -405,4 +392,2 @@ setContent (content) { | ||
this.trigger('media.update') | ||
// Trigger event for widget system | ||
setTimeout(() => this.pm.signal('draw'), 0) | ||
} | ||
@@ -409,0 +394,0 @@ _applyTransform (content) { |
import {expect} from 'chai' | ||
import {Node} from 'prosemirror/src/model/node' | ||
import {Node} from 'prosemirror/dist/model/node' | ||
@@ -5,0 +5,0 @@ import DocToGrid from '../../src/convert/doc-to-grid' |
@@ -26,3 +26,3 @@ import {expect} from 'chai' | ||
, 'type': 'image' | ||
, 'height': 50 | ||
, initialHeight: 72 | ||
, initialFocus: false | ||
@@ -59,3 +59,3 @@ } | ||
, 'type': 'video' | ||
, 'height': 50 | ||
, initialHeight: 72 | ||
, initialFocus: false | ||
@@ -69,4 +69,4 @@ } | ||
it('correctly converts Grid content to Doc', function () { | ||
const doc = GridToDoc(fixture) | ||
expect(doc.toJSON()).to.deep.equal(expected) | ||
const doc = GridToDoc(fixture).toJSON() | ||
expect(doc).to.deep.equal(expected) | ||
}) | ||
@@ -90,3 +90,3 @@ }) | ||
, 'type': 'image' | ||
, 'height': 50 | ||
, initialHeight: 72 | ||
, initialFocus: false | ||
@@ -100,3 +100,3 @@ } | ||
, 'type': 'video' | ||
, 'height': 50 | ||
, initialHeight: 72 | ||
, initialFocus: false | ||
@@ -110,6 +110,6 @@ } | ||
it('spaces with empty paragraphs', function () { | ||
const doc = GridToDoc(fixture) | ||
expect(doc.toJSON()).to.deep.equal(expected) | ||
const doc = GridToDoc(fixture).toJSON() | ||
expect(doc).to.deep.equal(expected) | ||
}) | ||
}) | ||
}) |
@@ -472,3 +472,5 @@ import {expect} from 'chai' | ||
function onMount () { | ||
ed.execCommand('ed_upload_image') | ||
setTimeout(function () { | ||
ed.execCommand('ed_upload_image') | ||
}, 100) | ||
} | ||
@@ -475,0 +477,0 @@ |
import {expect} from 'chai' | ||
import {mountApp, unmountApp} from '../../src/ed' | ||
@@ -7,3 +6,3 @@ | ||
describe('PluginWidget', function () { | ||
let mount, ed, PluginWidget | ||
let mount, ed, plugin | ||
const fixture = | ||
@@ -32,6 +31,9 @@ [ {type: 'h1', html: '<h1>Title</h1>', metadata: {starred: true}} | ||
, onRequestCoverUpload: function () {} | ||
, onMount: | ||
function (p) { | ||
plugin = p | ||
done() | ||
} | ||
} | ||
) | ||
PluginWidget = ed._store.editableView.plugins[0] | ||
ed._store.on('plugin.widget.initialized', done) | ||
}) | ||
@@ -64,3 +66,3 @@ afterEach(function () { | ||
it('has mounted widget', function () { | ||
const widget = PluginWidget.widgets['0000'] | ||
const widget = plugin.widgets['0000'] | ||
const status = widget.el.querySelector('.Placeholder-status') | ||
@@ -75,3 +77,3 @@ expect(widget).to.exist | ||
ed._store.on('media.update', function () { | ||
const widget = PluginWidget.widgets['0000'] | ||
const widget = plugin.widgets['0000'] | ||
const status = widget.el.querySelector('.Placeholder-status') | ||
@@ -91,3 +93,3 @@ expect(widget.type).to.equal('placeholder') | ||
it('updates placeholder widget failed via setContent', function (done) { | ||
const widget = PluginWidget.widgets['0000'] | ||
const widget = plugin.widgets['0000'] | ||
const el = widget.el.querySelector('.Placeholder') | ||
@@ -109,3 +111,3 @@ ed._store.on('media.update', function () { | ||
ed._store.on('media.update', function () { | ||
const widget = PluginWidget.widgets['0000'] | ||
const widget = plugin.widgets['0000'] | ||
const status = widget.el.querySelector('.Placeholder-status') | ||
@@ -120,3 +122,3 @@ expect(widget.type).to.equal('placeholder') | ||
it('updates placeholder widget failed true via updatePlaceholder', function (done) { | ||
const widget = PluginWidget.widgets['0000'] | ||
const widget = plugin.widgets['0000'] | ||
const el = widget.el.querySelector('.Placeholder') | ||
@@ -133,6 +135,6 @@ ed._store.on('media.update', function () { | ||
// Widget change is async | ||
ed.pm.on('ed.plugin.widget.one.initialized', function (id) { | ||
ed._store.on('plugin.widget.one.initialized', function (id) { | ||
expect(id).to.equal('0000') | ||
const widget = PluginWidget.widgets['0000'] | ||
const widget = plugin.widgets['0000'] | ||
expect(widget).to.exist | ||
@@ -139,0 +141,0 @@ expect(widget.type).to.equal('image') |
@@ -20,4 +20,3 @@ var webpack = require('webpack') | ||
path.resolve(__dirname, 'demo'), | ||
path.resolve(__dirname, 'src'), | ||
path.resolve(__dirname, 'node_modules', 'prosemirror') | ||
path.resolve(__dirname, 'src') | ||
] | ||
@@ -32,3 +31,3 @@ }, | ||
} else { | ||
entry.ed = './src/ed.js' | ||
entry.build = './src/ed.js' | ||
} | ||
@@ -35,0 +34,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
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
7132259
138
12816
33
247
+ Addedbrowserkeymap@1.0.1(transitive)
+ Addedmarkdown-it@6.1.1(transitive)
+ Addedprosemirror@0.8.3(transitive)
+ Addedsubscription@3.0.0(transitive)
- Removedbrowserkeymap@0.1.0(transitive)
- Removedmarkdown-it@4.4.0(transitive)
- Removedprosemirror@0.7.0(transitive)
Updatedlodash@^4.13.1
Updatedprosemirror@^0.8.2
Updatedrebass@^0.2.7