New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@the-grid/ed

Package Overview
Dependencies
Maintainers
18
Versions
138
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@the-grid/ed - npm Package Compare versions

Comparing version 0.16.0 to 0.17.0

dist/build.js

8

CHANGES.md
## 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 @@

14

demo/demo.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc