prosemirror-state
Advanced tools
Comparing version 0.11.1 to 0.12.0
@@ -8,2 +8,3 @@ ;var assign; | ||
exports.Plugin = require("./plugin").Plugin | ||
;var assign$1; | ||
((assign$1 = require("./plugin"), exports.Plugin = assign$1.Plugin, exports.PluginKey = assign$1.PluginKey)) |
@@ -1,56 +0,31 @@ | ||
function copyObj(from, to) { | ||
for (var prop in from) to[prop] = from[prop] | ||
return to | ||
} | ||
// ::- Plugins wrap extra functionality that can be added to an | ||
// editor. They can define new [state fields](#state.StateField), and | ||
// add [view props](#view.EditorProps). | ||
// | ||
// There are two ways to use plugins. The easiest way is to have a | ||
// factory function that simply creates instances of this class. This | ||
// creates a non-unique plugin, of which multiple instances can be | ||
// added to a single editor. | ||
// | ||
// The alternative is to have a single plugin instance, and optionally | ||
// derive different configurations from it using | ||
// [`configure`](#state.Plugin.configure). This creates a _unique_ | ||
// plugin, which means that an error is raised when multiple instances | ||
// are added to a single editor. This is appropriate if your plugin | ||
// defines state fields, for example. You can find the instance of | ||
// such a plugin in a state by calling its | ||
// [`find`](#state.Plugin.find) method. | ||
var Plugin = function Plugin(options, root) { | ||
var Plugin = function Plugin(options) { | ||
var this$1 = this; | ||
// :: EditorProps | ||
// The props exported by this plugin. | ||
this.props = options.props || {} | ||
this.props = {} | ||
if (options.props) { for (var prop in options.props) { | ||
var val = options.props[prop] | ||
if (val instanceof Function) { val = val.bind(this$1) } | ||
this$1.props[prop] = val | ||
} } | ||
// :: Object | ||
// The plugin's configuration object. | ||
this.config = options.config || {} | ||
this.options = options | ||
this.root = root || this | ||
this.key = options.key ? options.key.key : createKey("plugin") | ||
}; | ||
// :: (Object) → Plugin | ||
// Create a reconfigured instance of this plugin. Any config fields | ||
// not listed in the given object are inherited from the original | ||
// configuration. | ||
Plugin.prototype.configure = function configure (config) { | ||
return new Plugin(copyObj({ | ||
config: copyObj(config, copyObj(this.config, {})) | ||
}, copyObj(this.options, {})), this.root) | ||
}; | ||
// :: (EditorState) → ?Plugin | ||
// Find the instance of this plugin in a given editor state, if it | ||
// exists. Note that this only works if the plugin in the state is | ||
// either this exact plugin, or they both share a common ancestor | ||
// through [`configure`](#state.Plugin.configure) calls. | ||
Plugin.prototype.find = function find (state) { return state._config.findPlugin(this) }; | ||
// :: (EditorState) → any | ||
// Get the state field for this plugin. | ||
Plugin.prototype.getState = function getState (state) { return state[this.key] }; | ||
exports.Plugin = Plugin | ||
// StateField:: interface<T> | ||
// A plugin may provide a set of state fields, as an object (under its | ||
// `stateFields` property) mapping field names to description objects | ||
// of this type. | ||
// A plugin may provide a state field (under its `state` property) of | ||
// this type, which describes the state it wants to keep. Functions | ||
// provided here are always called with the plugin instance as their | ||
// `this` binding. | ||
// | ||
@@ -75,1 +50,27 @@ // init:: (config: Object, instance: EditorState) → T | ||
// `state` argument is again a half-initialized state. | ||
var keys = Object.create(null) | ||
function createKey(name) { | ||
if (name in keys) { return name + "$" + ++keys[name] } | ||
keys[name] = 0 | ||
return name + "$" | ||
} | ||
// ::- A key is used to [tag](#state.Plugin.constructor^options.key) | ||
// plugins in a way that makes it possible to find them, given an | ||
// editor state. Assigning a key does mean only one plugin of that | ||
// type can be active in a state. | ||
var PluginKey = function PluginKey(name) { | ||
if ( name === void 0 ) name = "key"; | ||
this.key = createKey(name) }; | ||
// :: (EditorState) → ?Plugin | ||
// Get the active plugin with this key, if any, from an editor | ||
// state. | ||
PluginKey.prototype.get = function get (state) { return state.config.pluginsByKey[this.key] }; | ||
// :: (EditorState) → ?any | ||
// Get the plugin's state from an editor state. | ||
PluginKey.prototype.getState = function getState (state) { return state[this.key] }; | ||
exports.PluginKey = PluginKey |
@@ -31,3 +31,3 @@ // ::- Superclass for editor selections. | ||
var action = {type: "selection", selection: this} | ||
if (options) for (var prop in options) action[prop] = options[prop] | ||
if (options) { for (var prop in options) { action[prop] = options[prop] } } | ||
return action | ||
@@ -54,3 +54,3 @@ }; | ||
: findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly) | ||
if (inner) return inner | ||
if (inner) { return inner } | ||
@@ -61,3 +61,3 @@ for (var depth = $pos.depth - 1; depth >= 0; depth--) { | ||
: findSelectionIn($pos.node(0), $pos.node(depth), $pos.after(depth + 1), $pos.index(depth) + 1, dir, textOnly) | ||
if (found) return found | ||
if (found) { return found } | ||
} | ||
@@ -74,3 +74,3 @@ }; | ||
var result = this.findFrom($pos, bias) || this.findFrom($pos, -bias) | ||
if (!result) throw new RangeError("Searching for selection in invalid document " + $pos.node(0)) | ||
if (!result) { throw new RangeError("Searching for selection in invalid document " + $pos.node(0)) } | ||
return result | ||
@@ -112,3 +112,3 @@ }; | ||
if (foundAnchor && foundHead) | ||
found = new TextSelection(foundAnchor.$anchor, foundHead.$head) | ||
{ found = new TextSelection(foundAnchor.$anchor, foundHead.$head) } | ||
} | ||
@@ -120,5 +120,5 @@ return found | ||
if (json.anchor != null) | ||
return {head: mapping.map(json.head), anchor: mapping.map(json.anchor)} | ||
{ return {head: mapping.map(json.head), anchor: mapping.map(json.anchor)} } | ||
else | ||
return {node: mapping.map(json.node), after: mapping.map(json.after, -1)} | ||
{ return {node: mapping.map(json.node), after: mapping.map(json.after, -1)} } | ||
}; | ||
@@ -135,8 +135,8 @@ | ||
var $anchor = doc.resolve(json.anchor), $head = doc.resolve(json.head) | ||
if ($anchor.parent.isTextblock && $head.parent.isTextblock) return new TextSelection($anchor, $head) | ||
else return Selection.between($anchor, $head) | ||
if ($anchor.parent.isTextblock && $head.parent.isTextblock) { return new TextSelection($anchor, $head) } | ||
else { return Selection.between($anchor, $head) } | ||
} else { | ||
var $pos = doc.resolve(json.node), after = $pos.nodeAfter | ||
if (after && json.after == json.pos + after.nodeSize && isSelectable(after)) return new NodeSelection($pos) | ||
else return Selection.near($pos) | ||
if (after && json.after == json.pos + after.nodeSize && isSelectable(after)) { return new NodeSelection($pos) } | ||
else { return Selection.near($pos) } | ||
} | ||
@@ -184,3 +184,3 @@ }; | ||
var $head = doc.resolve(mapping.map(this.head)) | ||
if (!$head.parent.isTextblock) return Selection.near($head) | ||
if (!$head.parent.isTextblock) { return Selection.near($head) } | ||
var $anchor = doc.resolve(mapping.map(this.anchor)) | ||
@@ -221,7 +221,6 @@ return new TextSelection($anchor.parent.isTextblock ? $anchor : $head, $head) | ||
NodeSelection.prototype.map = function map (doc, mapping) { | ||
var $from = doc.resolve(mapping.map(this.from, 1)) | ||
var to = mapping.map(this.to, -1) | ||
var node = $from.nodeAfter | ||
if (node && to == $from.pos + node.nodeSize && isSelectable(node)) | ||
return new NodeSelection($from) | ||
var from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1) | ||
var $from = doc.resolve(from.pos), node = $from.nodeAfter | ||
if (!from.deleted && !to.deleted && node && to.pos == from.pos + node.nodeSize && isSelectable(node)) | ||
{ return new NodeSelection($from) } | ||
return Selection.near($from) | ||
@@ -244,3 +243,3 @@ }; | ||
function findSelectionIn(doc, node, pos, index, dir, text) { | ||
if (node.isTextblock) return new TextSelection(doc.resolve(pos)) | ||
if (node.isTextblock) { return new TextSelection(doc.resolve(pos)) } | ||
for (var i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) { | ||
@@ -250,3 +249,3 @@ var child = node.child(i) | ||
var inner = findSelectionIn(doc, child, pos + dir, dir < 0 ? child.childCount : 0, dir, text) | ||
if (inner) return inner | ||
if (inner) { return inner } | ||
} else if (!text && isSelectable(child)) { | ||
@@ -253,0 +252,0 @@ return new NodeSelection(doc.resolve(pos - (dir < 0 ? child.nodeSize : 0))) |
@@ -20,8 +20,10 @@ var ref = require("prosemirror-model"); | ||
var FieldDesc = function FieldDesc(name, desc) { | ||
function bind(f, self) { | ||
return !self || !f ? f : f.bind(self) | ||
} | ||
var FieldDesc = function FieldDesc(name, desc, self) { | ||
this.name = name | ||
this.init = desc.init | ||
this.applyAction = desc.applyAction | ||
this.toJSON = desc.toJSON | ||
this.fromJSON = desc.fromJSON | ||
this.init = bind(desc.init, self) | ||
this.applyAction = bind(desc.applyAction, self) | ||
}; | ||
@@ -32,7 +34,5 @@ | ||
init: function init(config) { return config.doc || config.schema.nodes.doc.createAndFill() }, | ||
applyAction: function applyAction(state, action) { | ||
return action.type == "transform" ? action.transform.doc : state.doc | ||
}, | ||
toJSON: function toJSON(value) { return value.toJSON() }, | ||
fromJSON: function fromJSON(config, json) { return Node.fromJSON(config.schema, json) } | ||
applyAction: function applyAction(action, doc) { | ||
return action.type == "transform" ? action.transform.doc : doc | ||
} | ||
}), | ||
@@ -42,11 +42,9 @@ | ||
init: function init$1(config, instance) { return config.selection || Selection.atStart(instance.doc) }, | ||
applyAction: function applyAction$1(state, action) { | ||
applyAction: function applyAction$1(action, selection) { | ||
if (action.type == "transform") | ||
return action.selection || state.selection.map(action.transform.doc, action.transform.mapping) | ||
{ return action.selection || selection.map(action.transform.doc, action.transform.mapping) } | ||
if (action.type == "selection") | ||
return action.selection | ||
return state.selection | ||
}, | ||
toJSON: function toJSON$1(value) { return value.toJSON() }, | ||
fromJSON: function fromJSON$1(_, json, instance) { return Selection.fromJSON(instance.doc, json) } | ||
{ return action.selection } | ||
return selection | ||
} | ||
}), | ||
@@ -56,10 +54,10 @@ | ||
init: function init$2() { return null }, | ||
applyAction: function applyAction$2(state, action) { | ||
if (action.type == "transform") return action.selection ? null : state.storedMarks | ||
if (action.type == "selection") return null | ||
applyAction: function applyAction$2(action, storedMarks, state) { | ||
if (action.type == "transform") { return action.selection ? null : storedMarks } | ||
if (action.type == "selection") { return null } | ||
if (action.type == "addStoredMark" && state.selection.empty) | ||
return action.mark.addToSet(state.storedMarks || currentMarks(state.doc, state.selection)) | ||
{ return action.mark.addToSet(storedMarks || currentMarks(state.doc, state.selection)) } | ||
if (action.type == "removeStoredMark" && state.selection.empty) | ||
return action.markType.removeFromSet(state.storedMarks || currentMarks(state.doc, state.selection)) | ||
return state.storedMarks | ||
{ return action.markType.removeFromSet(storedMarks || currentMarks(state.doc, state.selection)) } | ||
return storedMarks | ||
} | ||
@@ -70,14 +68,13 @@ }), | ||
init: function init$3() { return ViewState.initial }, | ||
applyAction: function applyAction$3(state, action) { | ||
var view = state.view | ||
applyAction: function applyAction$3(action, view) { | ||
if (action.type == "transform") | ||
return new ViewState(view.inDOMChange, | ||
{ return new ViewState(view.inDOMChange, | ||
view.domChangeMapping && view.domChangeMapping.copy().appendMapping(action.transform.mapping), | ||
action.scrollIntoView ? true : action.selection ? false : view.scrollToSelection) | ||
action.scrollIntoView ? true : action.selection ? false : view.scrollToSelection) } | ||
if (action.type == "selection") | ||
return new ViewState(view.inDOMChange, view.domChangeMapping, action.scrollIntoView) | ||
{ return new ViewState(view.inDOMChange, view.domChangeMapping, action.scrollIntoView) } | ||
if (action.type == "startDOMChange") | ||
return new ViewState(action.id, new Mapping, view.scrollToSelection) | ||
{ return new ViewState(action.id, new Mapping, view.scrollToSelection) } | ||
if (action.type == "endDOMChange") | ||
return new ViewState(null, null, view.scrollToSelection) | ||
{ return new ViewState(null, null, view.scrollToSelection) } | ||
return view | ||
@@ -92,2 +89,4 @@ } | ||
// Object wrapping the part of a state object that stays the same | ||
// across actions. Stored in the state's `config` property. | ||
var Configuration = function Configuration(schema, plugins) { | ||
@@ -99,36 +98,13 @@ var this$1 = this; | ||
this.plugins = [] | ||
if (plugins) plugins.forEach(function (plugin) { return this$1.addPlugin(plugin); }) | ||
this.pluginsByKey = Object.create(null) | ||
if (plugins) { plugins.forEach(function (plugin) { | ||
if (this$1.pluginsByKey[plugin.key]) | ||
{ throw new RangeError("Adding different instances of a keyed plugin (" + plugin.key + ")") } | ||
this$1.plugins.push(plugin) | ||
this$1.pluginsByKey[plugin.key] = plugin | ||
if (plugin.options.state) | ||
{ this$1.fields.push(new FieldDesc(plugin.key, plugin.options.state, plugin)) } | ||
}) } | ||
}; | ||
Configuration.prototype.addPlugin = function addPlugin (plugin) { | ||
var this$1 = this; | ||
var deps = plugin.options.dependencies, found | ||
if (deps) deps.forEach(function (plugin) { return this$1.addPlugin(plugin); }) | ||
if (found = this.findPlugin(plugin)) { | ||
if (found == plugin) return | ||
throw new RangeError("Adding different configurations of the same plugin") | ||
} | ||
this.plugins.push(plugin) | ||
var fields = plugin.options.stateFields | ||
if (fields) var loop = function ( name ) { | ||
if (fields.hasOwnProperty(name)) { | ||
if (name == "_config" || EditorState.prototype.hasOwnProperty(name) || | ||
this$1.fields.some(function (field) { return field.name == name; })) | ||
throw new Error("Conflicting definition for state property " + name) | ||
this$1.fields.push(new FieldDesc(name, fields[name])) | ||
} | ||
}; | ||
for (var name in fields) loop( name ); | ||
}; | ||
Configuration.prototype.findPlugin = function findPlugin (plugin) { | ||
var this$1 = this; | ||
for (var i = 0; i < this.plugins.length; i++) | ||
if (this$1.plugins[i].root == plugin.root) return this$1.plugins[i] | ||
}; | ||
// ::- The state of a ProseMirror editor is represented by an object | ||
@@ -142,3 +118,3 @@ // of this type. This is a persistent data structure—it isn't updated, | ||
var EditorState = function EditorState(config) { | ||
this._config = config | ||
this.config = config | ||
}; | ||
@@ -161,3 +137,3 @@ | ||
prototypeAccessors.schema.get = function () { | ||
return this._config.schema | ||
return this.config.schema | ||
}; | ||
@@ -168,3 +144,3 @@ | ||
prototypeAccessors.plugins.get = function () { | ||
return this._config.plugins | ||
return this.config.plugins | ||
}; | ||
@@ -177,5 +153,7 @@ | ||
var newInstance = new EditorState(this._config), fields = this._config.fields | ||
for (var i = 0; i < fields.length; i++) | ||
newInstance[fields[i].name] = fields[i].applyAction(this$1, action) | ||
var newInstance = new EditorState(this.config), fields = this.config.fields | ||
for (var i = 0; i < fields.length; i++) { | ||
var field = fields[i] | ||
newInstance[field.name] = field.applyAction(action, this$1[field.name], this$1) | ||
} | ||
return newInstance | ||
@@ -200,3 +178,3 @@ }; | ||
for (var i = 0; i < $config.fields.length; i++) | ||
instance[$config.fields[i].name] = $config.fields[i].init(config, instance) | ||
{ instance[$config.fields[i].name] = $config.fields[i].init(config, instance) } | ||
return instance | ||
@@ -217,44 +195,54 @@ }; | ||
var fields = $config.fields, instance = new EditorState($config) | ||
var loop = function ( i ) { | ||
for (var i = 0; i < fields.length; i++) { | ||
var name = fields[i].name | ||
if (this$1._config.fields.some(function (f) { return f.name == name; })) | ||
instance[name] = this$1[name] | ||
else | ||
instance[name] = fields[i].init(config, instance) | ||
}; | ||
for (var i = 0; i < fields.length; i++) loop( i ); | ||
instance[name] = this$1.hasOwnProperty(name) ? this$1[name] : fields[i].init(config, instance) | ||
} | ||
return instance | ||
}; | ||
// :: (?Object) → Object | ||
// Convert this state to a JSON-serializable object. When the | ||
// `ignore` option is given, it is interpreted as an array of field | ||
// names that should not be serialized. | ||
EditorState.prototype.toJSON = function toJSON (options) { | ||
// :: (?Object<Plugin>) → Object | ||
// Serialize this state to JSON. If you want to serialize the state | ||
// of plugins, pass an object mapping property names to use in the | ||
// resulting JSON object to plugin objects. | ||
EditorState.prototype.toJSON = function toJSON (pluginFields) { | ||
var this$1 = this; | ||
var result = {}, fields = this._config.fields | ||
var ignore = options && options.ignore || [] | ||
for (var i = 0; i < fields.length; i++) { | ||
var field = fields[i] | ||
var json = field.toJSON && ignore.indexOf(field.name) == -1 ? field.toJSON(this$1[field.name]) : null | ||
if (json != null) result[field.name] = json | ||
} | ||
var result = {doc: this.doc.toJSON(), selection: this.selection.toJSON()} | ||
if (pluginFields) { for (var prop in pluginFields) { | ||
if (prop == "doc" || prop == "selection") | ||
{ throw new RangeError("The JSON fields `doc` and `selection` are reserved") } | ||
var plugin = pluginFields[prop], state = plugin.options.state | ||
if (state && state.toJSON) { result[prop] = state.toJSON.call(plugin, this$1[plugin.key]) } | ||
} } | ||
return result | ||
}; | ||
// :: (Object, Object) → EditorState | ||
// :: (Object, Object, ?Object<Plugin>) → EditorState | ||
// Deserialize a JSON representation of a state. `config` should | ||
// have at least a `schema` field, and should contain array of | ||
// plugins to initialize the state with. It is also passed as | ||
// starting configuration for fields that were not serialized. | ||
EditorState.fromJSON = function fromJSON (config, json) { | ||
if (!config.schema) throw new RangeError("Required config field 'schema' missing") | ||
var $config = new Configuration(config.schema, config.plugins), fields = $config.fields, instance = new EditorState($config) | ||
for (var i = 0; i < fields.length; i++) { | ||
var field = fields[i], value = json[field.name] | ||
if (value == null || !field.fromJSON) instance[field.name] = field.init(config, instance) | ||
else instance[field.name] = field.fromJSON(config, value, instance) | ||
} | ||
// plugins to initialize the state with. `pluginFields` can be used | ||
// to deserialize the state of plugins, by associating plugin | ||
// instances with the property names they use in the JSON object. | ||
EditorState.fromJSON = function fromJSON (config, json, pluginFields) { | ||
if (!config.schema) { throw new RangeError("Required config field 'schema' missing") } | ||
var $config = new Configuration(config.schema, config.plugins) | ||
var instance = new EditorState($config) | ||
$config.fields.forEach(function (field) { | ||
if (field.name == "doc") { | ||
instance.doc = Node.fromJSON(config.schema, json.doc) | ||
} else if (field.name == "selection") { | ||
instance.selection = Selection.fromJSON(instance.doc, json.selection) | ||
} else { | ||
if (pluginFields) { for (var prop in pluginFields) { | ||
var plugin = pluginFields[prop], state = plugin.options.state | ||
if (plugin.key == field.name && state && state.fromJSON && | ||
Object.prototype.hasOwnProperty.call(json, prop)) { | ||
// This field belongs to a plugin mapped to a JSON field, read it from there. | ||
instance[field.name] = state.fromJSON.call(plugin, config, json[prop], instance) | ||
return | ||
} | ||
} } | ||
instance[field.name] = field.init(config, instance) | ||
} | ||
}) | ||
return instance | ||
@@ -287,2 +275,5 @@ }; | ||
// | ||
// time:: number | ||
// The timestamp at which the change was made. | ||
// | ||
// scrollIntoView:: ?bool | ||
@@ -289,0 +280,0 @@ // When true, the next display update will scroll the cursor into |
@@ -62,3 +62,3 @@ var ref = require("prosemirror-model"); | ||
if (node && node.isInline && inheritMarks !== false) | ||
node = node.mark(this.state.storedMarks || this.doc.marksAt(from, to > from)) | ||
{ node = node.mark(this.state.storedMarks || this.doc.marksAt(from, to > from)) } | ||
var fragment = Fragment.from(node) | ||
@@ -84,3 +84,3 @@ | ||
var point = insertPoint(this.doc, from, node.type, node.attrs) | ||
if (point != null) from = to = point | ||
if (point != null) { from = to = point } | ||
} | ||
@@ -114,5 +114,5 @@ | ||
if (useSel) | ||
this.replaceSelection(node, false) | ||
{ this.replaceSelection(node, false) } | ||
else | ||
this.replaceWith(from, to, node) | ||
{ this.replaceWith(from, to, node) } | ||
@@ -132,4 +132,5 @@ if (text && useSel) { | ||
transform: this, | ||
selection: this.selectionSet ? this.selection : null} | ||
if (options) for (var prop in options) action[prop] = options[prop] | ||
selection: this.selectionSet ? this.selection : null, | ||
time: Date.now()} | ||
if (options) { for (var prop in options) { action[prop] = options[prop] } } | ||
return action | ||
@@ -136,0 +137,0 @@ }; |
{ | ||
"name": "prosemirror-state", | ||
"version": "0.11.1", | ||
"version": "0.12.0", | ||
"description": "ProseMirror editor state", | ||
@@ -19,4 +19,4 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"prosemirror-model": "^0.11.0", | ||
"prosemirror-transform": "^0.11.0" | ||
"prosemirror-model": "^0.12.0", | ||
"prosemirror-transform": "^0.12.0" | ||
}, | ||
@@ -23,0 +23,0 @@ "devDependencies": { |
@@ -10,2 +10,2 @@ ;({Selection: exports.Selection, | ||
exports.Plugin = require("./plugin").Plugin | ||
;({Plugin: exports.Plugin, PluginKey: exports.PluginKey} = require("./plugin")) |
@@ -1,23 +0,4 @@ | ||
function copyObj(from, to) { | ||
for (let prop in from) to[prop] = from[prop] | ||
return to | ||
} | ||
// ::- Plugins wrap extra functionality that can be added to an | ||
// editor. They can define new [state fields](#state.StateField), and | ||
// add [view props](#view.EditorProps). | ||
// | ||
// There are two ways to use plugins. The easiest way is to have a | ||
// factory function that simply creates instances of this class. This | ||
// creates a non-unique plugin, of which multiple instances can be | ||
// added to a single editor. | ||
// | ||
// The alternative is to have a single plugin instance, and optionally | ||
// derive different configurations from it using | ||
// [`configure`](#state.Plugin.configure). This creates a _unique_ | ||
// plugin, which means that an error is raised when multiple instances | ||
// are added to a single editor. This is appropriate if your plugin | ||
// defines state fields, for example. You can find the instance of | ||
// such a plugin in a state by calling its | ||
// [`find`](#state.Plugin.find) method. | ||
class Plugin { | ||
@@ -31,40 +12,35 @@ // :: (Object) | ||
// The [view props](#view.EditorProps) added by this plugin. | ||
// Note that the [`onAction`](#view.EditorProps.onAction) and | ||
// [`state`](#view.EditorProps.state] props can't be defined by | ||
// plugins, only by the main props object. Props that are | ||
// functions will be bound to have the plugin instance as their | ||
// `this` binding. | ||
// | ||
// stateFields:: ?Object<StateField> | ||
// Extra [state fields](#state.StateField) defined by this plugin. | ||
// state:: ?StateField | ||
// A [state field](#state.StateField) defined by this plugin. | ||
// | ||
// config:: ?Object | ||
// A set of plugin-specific configuration parameters used by | ||
// this plugin. | ||
// | ||
// dependencies:: ?[Plugin] | ||
// A set of plugins that should automatically be added to the | ||
// plugin set when this plugin is added. | ||
constructor(options, root) { | ||
// key:: ?PluginKey | ||
// Can optionally be used to make this a keyed plugin. You can | ||
// have only one plugin with a given key in a given state, but | ||
// it is possible to access the plugin's configuration and state | ||
// through the key, without having access to the plugin instance | ||
// itself. | ||
constructor(options) { | ||
// :: EditorProps | ||
// The props exported by this plugin. | ||
this.props = options.props || {} | ||
this.props = {} | ||
if (options.props) for (let prop in options.props) { | ||
let val = options.props[prop] | ||
if (val instanceof Function) val = val.bind(this) | ||
this.props[prop] = val | ||
} | ||
// :: Object | ||
// The plugin's configuration object. | ||
this.config = options.config || {} | ||
this.options = options | ||
this.root = root || this | ||
this.key = options.key ? options.key.key : createKey("plugin") | ||
} | ||
// :: (Object) → Plugin | ||
// Create a reconfigured instance of this plugin. Any config fields | ||
// not listed in the given object are inherited from the original | ||
// configuration. | ||
configure(config) { | ||
return new Plugin(copyObj({ | ||
config: copyObj(config, copyObj(this.config, {})) | ||
}, copyObj(this.options, {})), this.root) | ||
} | ||
// :: (EditorState) → ?Plugin | ||
// Find the instance of this plugin in a given editor state, if it | ||
// exists. Note that this only works if the plugin in the state is | ||
// either this exact plugin, or they both share a common ancestor | ||
// through [`configure`](#state.Plugin.configure) calls. | ||
find(state) { return state._config.findPlugin(this) } | ||
// :: (EditorState) → any | ||
// Get the state field for this plugin. | ||
getState(state) { return state[this.key] } | ||
} | ||
@@ -74,5 +50,6 @@ exports.Plugin = Plugin | ||
// StateField:: interface<T> | ||
// A plugin may provide a set of state fields, as an object (under its | ||
// `stateFields` property) mapping field names to description objects | ||
// of this type. | ||
// A plugin may provide a state field (under its `state` property) of | ||
// this type, which describes the state it wants to keep. Functions | ||
// provided here are always called with the plugin instance as their | ||
// `this` binding. | ||
// | ||
@@ -97,1 +74,29 @@ // init:: (config: Object, instance: EditorState) → T | ||
// `state` argument is again a half-initialized state. | ||
const keys = Object.create(null) | ||
function createKey(name) { | ||
if (name in keys) return name + "$" + ++keys[name] | ||
keys[name] = 0 | ||
return name + "$" | ||
} | ||
// ::- A key is used to [tag](#state.Plugin.constructor^options.key) | ||
// plugins in a way that makes it possible to find them, given an | ||
// editor state. Assigning a key does mean only one plugin of that | ||
// type can be active in a state. | ||
class PluginKey { | ||
// :: (?string) | ||
// Create a plugin key. | ||
constructor(name = "key") { this.key = createKey(name) } | ||
// :: (EditorState) → ?Plugin | ||
// Get the active plugin with this key, if any, from an editor | ||
// state. | ||
get(state) { return state.config.pluginsByKey[this.key] } | ||
// :: (EditorState) → ?any | ||
// Get the plugin's state from an editor state. | ||
getState(state) { return state[this.key] } | ||
} | ||
exports.PluginKey = PluginKey |
@@ -37,1 +37,2 @@ This module implements the state object of a ProseMirror editor, along | ||
@StateField | ||
@PluginKey |
@@ -190,4 +190,3 @@ // ::- Superclass for editor selections. | ||
// Create a node selection. Does not verify the validity of its | ||
// argument. Use `ProseMirror.setNodeSelection` for an easier, | ||
// error-checking way to create a node selection. | ||
// argument. | ||
constructor($from) { | ||
@@ -205,6 +204,5 @@ let $to = $from.plusOne() | ||
map(doc, mapping) { | ||
let $from = doc.resolve(mapping.map(this.from, 1)) | ||
let to = mapping.map(this.to, -1) | ||
let node = $from.nodeAfter | ||
if (node && to == $from.pos + node.nodeSize && isSelectable(node)) | ||
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1) | ||
let $from = doc.resolve(from.pos), node = $from.nodeAfter | ||
if (!from.deleted && !to.deleted && node && to.pos == from.pos + node.nodeSize && isSelectable(node)) | ||
return new NodeSelection($from) | ||
@@ -211,0 +209,0 @@ return Selection.near($from) |
157
src/state.js
@@ -17,9 +17,11 @@ const {Mark, Node} = require("prosemirror-model") | ||
function bind(f, self) { | ||
return !self || !f ? f : f.bind(self) | ||
} | ||
class FieldDesc { | ||
constructor(name, desc) { | ||
constructor(name, desc, self) { | ||
this.name = name | ||
this.init = desc.init | ||
this.applyAction = desc.applyAction | ||
this.toJSON = desc.toJSON | ||
this.fromJSON = desc.fromJSON | ||
this.init = bind(desc.init, self) | ||
this.applyAction = bind(desc.applyAction, self) | ||
} | ||
@@ -31,7 +33,5 @@ } | ||
init(config) { return config.doc || config.schema.nodes.doc.createAndFill() }, | ||
applyAction(state, action) { | ||
return action.type == "transform" ? action.transform.doc : state.doc | ||
}, | ||
toJSON(value) { return value.toJSON() }, | ||
fromJSON(config, json) { return Node.fromJSON(config.schema, json) } | ||
applyAction(action, doc) { | ||
return action.type == "transform" ? action.transform.doc : doc | ||
} | ||
}), | ||
@@ -41,11 +41,9 @@ | ||
init(config, instance) { return config.selection || Selection.atStart(instance.doc) }, | ||
applyAction(state, action) { | ||
applyAction(action, selection) { | ||
if (action.type == "transform") | ||
return action.selection || state.selection.map(action.transform.doc, action.transform.mapping) | ||
return action.selection || selection.map(action.transform.doc, action.transform.mapping) | ||
if (action.type == "selection") | ||
return action.selection | ||
return state.selection | ||
}, | ||
toJSON(value) { return value.toJSON() }, | ||
fromJSON(_, json, instance) { return Selection.fromJSON(instance.doc, json) } | ||
return selection | ||
} | ||
}), | ||
@@ -55,10 +53,10 @@ | ||
init() { return null }, | ||
applyAction(state, action) { | ||
if (action.type == "transform") return action.selection ? null : state.storedMarks | ||
applyAction(action, storedMarks, state) { | ||
if (action.type == "transform") return action.selection ? null : storedMarks | ||
if (action.type == "selection") return null | ||
if (action.type == "addStoredMark" && state.selection.empty) | ||
return action.mark.addToSet(state.storedMarks || currentMarks(state.doc, state.selection)) | ||
return action.mark.addToSet(storedMarks || currentMarks(state.doc, state.selection)) | ||
if (action.type == "removeStoredMark" && state.selection.empty) | ||
return action.markType.removeFromSet(state.storedMarks || currentMarks(state.doc, state.selection)) | ||
return state.storedMarks | ||
return action.markType.removeFromSet(storedMarks || currentMarks(state.doc, state.selection)) | ||
return storedMarks | ||
} | ||
@@ -69,4 +67,3 @@ }), | ||
init() { return ViewState.initial }, | ||
applyAction(state, action) { | ||
let view = state.view | ||
applyAction(action, view) { | ||
if (action.type == "transform") | ||
@@ -91,2 +88,4 @@ return new ViewState(view.inDOMChange, | ||
// Object wrapping the part of a state object that stays the same | ||
// across actions. Stored in the state's `config` property. | ||
class Configuration { | ||
@@ -97,27 +96,12 @@ constructor(schema, plugins) { | ||
this.plugins = [] | ||
if (plugins) plugins.forEach(plugin => this.addPlugin(plugin)) | ||
this.pluginsByKey = Object.create(null) | ||
if (plugins) plugins.forEach(plugin => { | ||
if (this.pluginsByKey[plugin.key]) | ||
throw new RangeError("Adding different instances of a keyed plugin (" + plugin.key + ")") | ||
this.plugins.push(plugin) | ||
this.pluginsByKey[plugin.key] = plugin | ||
if (plugin.options.state) | ||
this.fields.push(new FieldDesc(plugin.key, plugin.options.state, plugin)) | ||
}) | ||
} | ||
addPlugin(plugin) { | ||
let deps = plugin.options.dependencies, found | ||
if (deps) deps.forEach(plugin => this.addPlugin(plugin)) | ||
if (found = this.findPlugin(plugin)) { | ||
if (found == plugin) return | ||
throw new RangeError("Adding different configurations of the same plugin") | ||
} | ||
this.plugins.push(plugin) | ||
let fields = plugin.options.stateFields | ||
if (fields) for (let name in fields) if (fields.hasOwnProperty(name)) { | ||
if (name == "_config" || EditorState.prototype.hasOwnProperty(name) || | ||
this.fields.some(field => field.name == name)) | ||
throw new Error("Conflicting definition for state property " + name) | ||
this.fields.push(new FieldDesc(name, fields[name])) | ||
} | ||
} | ||
findPlugin(plugin) { | ||
for (let i = 0; i < this.plugins.length; i++) | ||
if (this.plugins[i].root == plugin.root) return this.plugins[i] | ||
} | ||
} | ||
@@ -134,3 +118,3 @@ | ||
constructor(config) { | ||
this._config = config | ||
this.config = config | ||
} | ||
@@ -151,3 +135,3 @@ | ||
get schema() { | ||
return this._config.schema | ||
return this.config.schema | ||
} | ||
@@ -158,3 +142,3 @@ | ||
get plugins() { | ||
return this._config.plugins | ||
return this.config.plugins | ||
} | ||
@@ -165,5 +149,7 @@ | ||
applyAction(action) { | ||
let newInstance = new EditorState(this._config), fields = this._config.fields | ||
for (let i = 0; i < fields.length; i++) | ||
newInstance[fields[i].name] = fields[i].applyAction(this, action) | ||
let newInstance = new EditorState(this.config), fields = this.config.fields | ||
for (let i = 0; i < fields.length; i++) { | ||
let field = fields[i] | ||
newInstance[field.name] = field.applyAction(action, this[field.name], this) | ||
} | ||
return newInstance | ||
@@ -204,6 +190,3 @@ } | ||
let name = fields[i].name | ||
if (this._config.fields.some(f => f.name == name)) | ||
instance[name] = this[name] | ||
else | ||
instance[name] = fields[i].init(config, instance) | ||
instance[name] = this.hasOwnProperty(name) ? this[name] : fields[i].init(config, instance) | ||
} | ||
@@ -213,13 +196,13 @@ return instance | ||
// :: (?Object) → Object | ||
// Convert this state to a JSON-serializable object. When the | ||
// `ignore` option is given, it is interpreted as an array of field | ||
// names that should not be serialized. | ||
toJSON(options) { | ||
let result = {}, fields = this._config.fields | ||
let ignore = options && options.ignore || [] | ||
for (let i = 0; i < fields.length; i++) { | ||
let field = fields[i] | ||
let json = field.toJSON && ignore.indexOf(field.name) == -1 ? field.toJSON(this[field.name]) : null | ||
if (json != null) result[field.name] = json | ||
// :: (?Object<Plugin>) → Object | ||
// Serialize this state to JSON. If you want to serialize the state | ||
// of plugins, pass an object mapping property names to use in the | ||
// resulting JSON object to plugin objects. | ||
toJSON(pluginFields) { | ||
let result = {doc: this.doc.toJSON(), selection: this.selection.toJSON()} | ||
if (pluginFields) for (let prop in pluginFields) { | ||
if (prop == "doc" || prop == "selection") | ||
throw new RangeError("The JSON fields `doc` and `selection` are reserved") | ||
let plugin = pluginFields[prop], state = plugin.options.state | ||
if (state && state.toJSON) result[prop] = state.toJSON.call(plugin, this[plugin.key]) | ||
} | ||
@@ -229,15 +212,30 @@ return result | ||
// :: (Object, Object) → EditorState | ||
// :: (Object, Object, ?Object<Plugin>) → EditorState | ||
// Deserialize a JSON representation of a state. `config` should | ||
// have at least a `schema` field, and should contain array of | ||
// plugins to initialize the state with. It is also passed as | ||
// starting configuration for fields that were not serialized. | ||
static fromJSON(config, json) { | ||
// plugins to initialize the state with. `pluginFields` can be used | ||
// to deserialize the state of plugins, by associating plugin | ||
// instances with the property names they use in the JSON object. | ||
static fromJSON(config, json, pluginFields) { | ||
if (!config.schema) throw new RangeError("Required config field 'schema' missing") | ||
let $config = new Configuration(config.schema, config.plugins), fields = $config.fields, instance = new EditorState($config) | ||
for (let i = 0; i < fields.length; i++) { | ||
let field = fields[i], value = json[field.name] | ||
if (value == null || !field.fromJSON) instance[field.name] = field.init(config, instance) | ||
else instance[field.name] = field.fromJSON(config, value, instance) | ||
} | ||
let $config = new Configuration(config.schema, config.plugins) | ||
let instance = new EditorState($config) | ||
$config.fields.forEach(field => { | ||
if (field.name == "doc") { | ||
instance.doc = Node.fromJSON(config.schema, json.doc) | ||
} else if (field.name == "selection") { | ||
instance.selection = Selection.fromJSON(instance.doc, json.selection) | ||
} else { | ||
if (pluginFields) for (let prop in pluginFields) { | ||
let plugin = pluginFields[prop], state = plugin.options.state | ||
if (plugin.key == field.name && state && state.fromJSON && | ||
Object.prototype.hasOwnProperty.call(json, prop)) { | ||
// This field belongs to a plugin mapped to a JSON field, read it from there. | ||
instance[field.name] = state.fromJSON.call(plugin, config, json[prop], instance) | ||
return | ||
} | ||
} | ||
instance[field.name] = field.init(config, instance) | ||
} | ||
}) | ||
return instance | ||
@@ -269,2 +267,5 @@ } | ||
// | ||
// time:: number | ||
// The timestamp at which the change was made. | ||
// | ||
// scrollIntoView:: ?bool | ||
@@ -271,0 +272,0 @@ // When true, the next display update will scroll the cursor into |
@@ -111,3 +111,4 @@ const {Fragment} = require("prosemirror-model") | ||
transform: this, | ||
selection: this.selectionSet ? this.selection : null} | ||
selection: this.selectionSet ? this.selection : null, | ||
time: Date.now()} | ||
if (options) for (let prop in options) action[prop] = options[prop] | ||
@@ -114,0 +115,0 @@ return action |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
65499
1350
+ Addedprosemirror-model@0.12.0(transitive)
+ Addedprosemirror-transform@0.12.1(transitive)
- Removedprosemirror-model@0.11.1(transitive)
- Removedprosemirror-transform@0.11.1(transitive)
Updatedprosemirror-model@^0.12.0