prosemirror
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -0,1 +1,35 @@ | ||
## [0.5.0](http://prosemirror.net/version/0.5.0.html) (2016-03-22) | ||
### Bug fixes | ||
ProseMirror now ignores most evens when not focused, so you can have | ||
focusable fields inside the editor. | ||
The Markdown serializer is now a lot more clever about serializing | ||
mixed inline styles. | ||
Event handlers unregistering themselves is now safe (used to skip next | ||
event handler). | ||
### New features | ||
The default command parameter prompt UI now shows the command label | ||
and a submit button. | ||
When joining an empty textblock with a non-empty one, the resulting | ||
block now gets the type of the non-empty one. | ||
Node types can now handle double clicks with a `handleDoubleClick` | ||
method. | ||
Undo and redo now restore the selection that was current when the | ||
history event was created. | ||
The collab module now fires a `"collabTransform"` event when receiving | ||
changes. | ||
The `"filterTransform"` event can now be used to cancel transforms. | ||
Node kinds can now specify both their super- and sub-kinds. | ||
## [0.4.0](http://prosemirror.net/version/0.4.0.html) (2016-02-24) | ||
@@ -2,0 +36,0 @@ |
@@ -45,5 +45,9 @@ # How to contribute | ||
([how to fork a repo](https://help.github.com/articles/fork-a-repo)) | ||
- Make your changes, and commit them | ||
- Follow the code style of the rest of the project (see below). Run | ||
`npm run lint` to verify that the linter is happy (you'll need to | ||
run `npm install` first). | ||
- If your changes are easy to test or likely to regress, add tests in | ||
@@ -53,8 +57,7 @@ the `test/` directory. Either put them in an existing `test-*.js` | ||
- Follow the code style of the rest of the project (see below). Run | ||
`npm run lint` to verify that the linter is happy (you'll need to | ||
run `npm install` first). | ||
- Make sure all tests pass. Run `npm run test` verify tests pass. | ||
Also run browser tests by starting the demo (`npm run demo`) and | ||
visiting [http://localhost:8080/test.html](http://localhost:8080/test.html) | ||
in a browser. | ||
- Make sure all tests pass. | ||
- Submit a pull request ([how to create a pull request](https://help.github.com/articles/fork-a-repo)). | ||
@@ -61,0 +64,0 @@ Don't put more than one feature/fix in a single pull request. |
@@ -16,2 +16,4 @@ "use strict"; | ||
var _transform = require("../transform"); | ||
var _rebase = require("./rebase"); | ||
@@ -151,18 +153,21 @@ | ||
value: function receive(steps) { | ||
var doc = this.versionDoc; | ||
var maps = steps.map(function (step) { | ||
var result = step.apply(doc); | ||
doc = result.doc; | ||
return result.map; | ||
var transform = new _transform.Transform(this.versionDoc); | ||
steps.forEach(function (step) { | ||
return transform.step(step); | ||
}); | ||
this.version += steps.length; | ||
this.versionDoc = doc; | ||
this.versionDoc = transform.doc; | ||
var rebased = (0, _rebase.rebaseSteps)(doc, maps, this.unconfirmedSteps, this.unconfirmedMaps); | ||
var rebased = (0, _rebase.rebaseSteps)(transform.doc, transform.maps, this.unconfirmedSteps, this.unconfirmedMaps); | ||
this.unconfirmedSteps = rebased.transform.steps.slice(); | ||
this.unconfirmedMaps = rebased.transform.maps.slice(); | ||
var selectionBefore = this.pm.selection; | ||
this.pm.updateDoc(rebased.doc, rebased.mapping); | ||
this.pm.history.rebased(maps, rebased.transform, rebased.positions); | ||
return maps; | ||
this.pm.history.rebased(transform.maps, rebased.transform, rebased.positions); | ||
// :: (transform: Transform, selectionBeforeTransform: Selection) #path=Collab#events#collabTransform | ||
// Signals that a transformation has been aplied to the editor. Passes the `Transform` and the selection | ||
// before the transform as arguments to the handler. | ||
this.signal("collabTransform", transform, selectionBefore); | ||
return transform.maps; | ||
} | ||
@@ -169,0 +174,0 @@ }]); |
@@ -84,3 +84,7 @@ "use strict"; | ||
after = around.child(cut.offset); | ||
if (before.type.canContainContent(after.type) && pm.tr.join(cut).apply(pm.apply.scroll) !== false) return; | ||
if (before.type.canContainContent(after.type)) { | ||
var tr = pm.tr.join(cut); | ||
if (tr.steps.length && before.size == 0 && !before.sameMarkup(after)) tr.setNodeType(cut.move(-1), after.type, after.attrs); | ||
if (tr.apply(pm.apply.scroll) !== false) return; | ||
} | ||
@@ -132,2 +136,10 @@ var conn = undefined; | ||
// If the node below has no content and the node above is | ||
// selectable, delete the node below and select the one above. | ||
if (before.type.contains == null && before.type.selectable && pm.doc.path(head.path).size == 0) { | ||
var tr = pm.tr.delete(cut, cut.move(1)).apply(pm.apply.scroll); | ||
pm.setNodeSelection(cut.move(-1)); | ||
return tr; | ||
} | ||
// If the node doesn't allow children, delete it | ||
@@ -377,3 +389,3 @@ if (before.type.contains == null) return pm.tr.delete(cut.move(-1), cut).apply(pm.apply.scroll); | ||
// | ||
// **Keybindings:** Alt-Left | ||
// **Keybindings:** Ctrl-[ | ||
baseCommands.lift = { | ||
@@ -404,3 +416,3 @@ label: "Lift out of enclosing block", | ||
}, | ||
keys: ["Alt-Left"] | ||
keys: ["Mod-["] | ||
}; | ||
@@ -588,3 +600,6 @@ | ||
keys: ["Left", "Mod-Left"] | ||
keys: { | ||
all: ["Left", "Mod-Left"], | ||
mac: ["Alt-Left"] | ||
} | ||
}; | ||
@@ -604,5 +619,12 @@ | ||
keys: ["Right", "Mod-Right"] | ||
keys: { | ||
all: ["Right", "Mod-Right"], | ||
mac: ["Alt-Left"] | ||
} | ||
}; | ||
// : (ProseMirror, number) | ||
// Check whether vertical selection motion would involve node | ||
// selections. If so, apply it (if not, the result is left to the | ||
// browser) | ||
function selectNodeVertically(pm, dir) { | ||
@@ -609,0 +631,0 @@ var _pm$selection17 = pm.selection; |
@@ -6,3 +6,6 @@ "use strict"; | ||
}); | ||
exports.applyDOMChange = applyDOMChange; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
exports.readDOMChange = readDOMChange; | ||
exports.textContext = textContext; | ||
@@ -17,2 +20,4 @@ exports.textInContext = textInContext; | ||
var _selection = require("./selection"); | ||
var _dompos = require("./dompos"); | ||
@@ -67,12 +72,24 @@ | ||
function applyDOMChange(pm) { | ||
function readDOMChange(pm) { | ||
var updated = parseNearSelection(pm); | ||
var changeStart = (0, _model.findDiffStart)(pm.doc.content, updated.content); | ||
if (changeStart) { | ||
var changeEnd = findDiffEndConstrained(pm.doc.content, updated.content, changeStart); | ||
// Mark nodes touched by this change as 'to be redrawn' | ||
markDirtyFor(pm, changeStart, changeEnd); | ||
var _ret = function () { | ||
var changeEnd = findDiffEndConstrained(pm.doc.content, updated.content, changeStart); | ||
// Mark nodes touched by this change as 'to be redrawn' | ||
markDirtyFor(pm, changeStart, changeEnd); | ||
pm.tr.replace(changeStart, changeEnd.a, updated, changeStart, changeEnd.b).apply(); | ||
return true; | ||
var near = undefined; | ||
// FIXME when we have a Slice type, just return replace info, & let caller inspect it | ||
if (pm.doc.path(changeStart.path).isTextblock && _model.Pos.samePath(changeStart.path, changeEnd.a.path) && !_model.Pos.samePath(changeStart.path, changeEnd.b.path) && (near = (0, _selection.findSelectionFrom)(updated, after(updated, changeStart), 1, true)) && !near.head.cmp(changeEnd.b)) return { | ||
v: { type: "enter" } | ||
};else return { | ||
v: { type: "replace", | ||
run: function run() { | ||
return pm.tr.replace(changeStart, changeEnd.a, updated, changeStart, changeEnd.b).apply(); | ||
} } | ||
}; | ||
}(); | ||
if ((typeof _ret === "undefined" ? "undefined" : _typeof(_ret)) === "object") return _ret.v; | ||
} else { | ||
@@ -83,2 +100,6 @@ return false; | ||
function after(doc, pos) { | ||
if (pos.offset < doc.path(pos.path).size) return pos.move(1);else return pos.shorten(null, 1); | ||
} | ||
function offsetBy(first, second, pos) { | ||
@@ -85,0 +106,0 @@ var same = (0, _tree.samePathDepth)(first, second); |
@@ -353,2 +353,7 @@ "use strict"; | ||
// :: (pm: ProseMirror, event: MouseEvent, path: [number], node: Node) → bool | ||
// #path=NodeType.prototype.handleDoubleClick | ||
// This works like [`handleClick`](#NodeType.handleClick), but is | ||
// called for double clicks instead. | ||
// :: (pm: ProseMirror, event: MouseEvent, path: [number], node: Node) → bool | ||
// #path=NodeType.prototype.handleContextMenu | ||
@@ -355,0 +360,0 @@ // |
@@ -24,8 +24,8 @@ "use strict"; | ||
onRender: function onRender(node, dom, offset) { | ||
if (!node.isText && node.type.contains == null) { | ||
dom.contentEditable = false; | ||
if (node.isBlock) dom.setAttribute("pm-leaf", "true"); | ||
if (node.isBlock) { | ||
if (node.type.contains == null) dom.setAttribute("pm-leaf", "true"); | ||
if (offset != null) dom.setAttribute("pm-offset", offset); | ||
if (node.isTextblock) adjustTrailingHacks(dom, node); | ||
if (dom.contentEditable == "false") dom = (0, _dom.elt)("div", dom); | ||
} | ||
if (node.isBlock && offset != null) dom.setAttribute("pm-offset", offset); | ||
if (node.isTextblock) adjustTrailingHacks(dom, node); | ||
@@ -32,0 +32,0 @@ return dom; |
@@ -27,2 +27,13 @@ "use strict"; | ||
var HistoryEvent = function HistoryEvent(steps, selection) { | ||
_classCallCheck(this, HistoryEvent); | ||
this.steps = steps; | ||
this.selection = selection; | ||
}; | ||
// Assists with remapping a step with other changes that have been | ||
// made since the step was first applied. | ||
var BranchRemapping = function () { | ||
@@ -34,2 +45,4 @@ function BranchRemapping(branch) { | ||
this.remap = new _transform.Remapping(); | ||
// Track the internal version of what step the current remapping collection | ||
// would put the content at. | ||
this.version = branch.version; | ||
@@ -39,2 +52,6 @@ this.mirrorBuffer = Object.create(null); | ||
// Add all position maps between the current version | ||
// and the desired version to the remapping collection. | ||
_createClass(BranchRemapping, [{ | ||
@@ -47,2 +64,6 @@ key: "moveToVersion", | ||
} | ||
// Add the next map at the current version to the | ||
// remapping collection. | ||
}, { | ||
@@ -69,5 +90,13 @@ key: "addNextMap", | ||
var workTime = 100, | ||
pauseTime = 150; | ||
// The number of milliseconds the compression worker has to compress. | ||
var workTime = 100; | ||
// The number of milliseconds to pause compression worker if it uses | ||
// all its work time. | ||
var pauseTime = 150; | ||
// Help compress steps in events for a branch. | ||
var CompressionWorker = function () { | ||
@@ -91,2 +120,5 @@ function CompressionWorker(doc, branch, callback) { | ||
// Compress steps in all events in the branch. | ||
_createClass(CompressionWorker, [{ | ||
@@ -103,9 +135,10 @@ key: "work", | ||
if (this.i == 0) return this.finish(); | ||
var event = this.branch.events[--this.i], | ||
outEvent = []; | ||
for (var j = event.length - 1; j >= 0; j--) { | ||
var _event$j = event[j]; | ||
var step = _event$j.step; | ||
var stepVersion = _event$j.version; | ||
var stepID = _event$j.id; | ||
var event = this.branch.events[--this.i]; | ||
var mappedSelection = event.selection && event.selection.map(this.doc, this.remap.remap); | ||
var outEvent = new HistoryEvent([], mappedSelection); | ||
for (var j = event.steps.length - 1; j >= 0; j--) { | ||
var _event$steps$j = event.steps[j]; | ||
var step = _event$steps$j.step; | ||
var stepVersion = _event$steps$j.version; | ||
var stepID = _event$steps$j.id; | ||
@@ -115,2 +148,4 @@ this.remap.moveToVersion(stepVersion); | ||
var mappedStep = step.map(this.remap.remap); | ||
// Combine contiguous delete steps. | ||
if (mappedStep && isDelStep(step)) { | ||
@@ -120,3 +155,3 @@ var extra = 0, | ||
while (j > 0) { | ||
var next = event[j - 1]; | ||
var next = event.steps[j - 1]; | ||
if (next.version != stepVersion - 1 || !isDelStep(next.step) || start.cmp(next.step.to)) break; | ||
@@ -138,3 +173,3 @@ extra += next.step.to.offset - next.step.from.offset; | ||
this.maps.push(result.map.invert()); | ||
outEvent.push(new InvertedStep(mappedStep, this.version, stepID)); | ||
outEvent.steps.push(new InvertedStep(mappedStep, this.version, stepID)); | ||
this.version--; | ||
@@ -144,4 +179,4 @@ } | ||
} | ||
if (outEvent.length) { | ||
outEvent.reverse(); | ||
if (outEvent.steps.length) { | ||
outEvent.steps.reverse(); | ||
this.events.push(outEvent); | ||
@@ -181,2 +216,3 @@ } | ||
// The minimum number of new steps before a compression is started. | ||
var compressStepCount = 150; | ||
@@ -213,7 +249,12 @@ | ||
} | ||
// : (Selection) | ||
// Create a new history event at tip of the branch. | ||
}, { | ||
key: "newEvent", | ||
value: function newEvent() { | ||
value: function newEvent(currentSelection) { | ||
this.abortCompression(); | ||
this.events.push([]); | ||
this.events.push(new HistoryEvent([], currentSelection)); | ||
while (this.events.length > this.maxDepth) { | ||
@@ -223,2 +264,8 @@ this.events.shift(); | ||
} | ||
// : (PosMap) | ||
// Add a position map to the branch, either representing one of the | ||
// changes recorded in the branch, or representing a non-history | ||
// change that the branch's changes must be mapped through. | ||
}, { | ||
@@ -234,2 +281,6 @@ key: "addMap", | ||
} | ||
// : () → bool | ||
// Whether the branch is empty (has no history events). | ||
}, { | ||
@@ -245,3 +296,3 @@ key: "empty", | ||
if (id == null) id = this.nextStepID++; | ||
this.events[this.events.length - 1].push(new InvertedStep(step, this.version, id)); | ||
this.events[this.events.length - 1].steps.push(new InvertedStep(step, this.version, id)); | ||
} | ||
@@ -262,3 +313,3 @@ | ||
// : (Node, bool) → ?{transform: Transform, ids: [number]} | ||
// : (Node, bool) → ?{transform: Transform, ids: [number], selection: Selection} | ||
// Pop the latest event off the branch's history and apply it | ||
@@ -279,10 +330,12 @@ // to a document transform, returning the transform and the step ID. | ||
for (var i = event.length - 1; i >= 0; i--) { | ||
var invertedStep = event[i], | ||
for (var i = event.steps.length - 1; i >= 0; i--) { | ||
var invertedStep = event.steps[i], | ||
step = invertedStep.step; | ||
if (!collapsing || invertedStep.version != remap.version) { | ||
collapsing = false; | ||
// Remap the step through any position mappings unrelated to | ||
// history (e.g. collaborative edits). | ||
remap.moveToVersion(invertedStep.version); | ||
step = step.map(remap.remap); | ||
step = step.map(remap.remap); | ||
var result = step && tr.step(step); | ||
@@ -294,3 +347,3 @@ if (result) { | ||
if (i > 0) remap.movePastStep(result); | ||
remap.movePastStep(result); | ||
} else { | ||
@@ -305,4 +358,6 @@ this.version--; | ||
} | ||
var selection = event.selection && event.selection.map(tr.doc, remap.remap); | ||
if (this.empty()) this.clear(true); | ||
return { transform: tr, ids: ids }; | ||
return { transform: tr, ids: ids, selection: selection }; | ||
} | ||
@@ -314,3 +369,3 @@ }, { | ||
var event = this.events[i]; | ||
if (event.length) return event[event.length - 1]; | ||
if (event.steps.length) return event.steps[event.steps.length - 1]; | ||
} | ||
@@ -333,6 +388,10 @@ } | ||
value: function findVersion(version) { | ||
// FIXME this is not accurate when the actual revision has fallen | ||
// off the end of the history. Current representation of versions | ||
// does not allow us to recognize that case. | ||
if (version.lastID == null) return { event: 0, step: 0 }; | ||
for (var i = this.events.length - 1; i >= 0; i--) { | ||
var event = this.events[i]; | ||
for (var j = event.length - 1; j >= 0; j--) { | ||
if (event[j].id <= version.lastID) return { event: i, step: j + 1 }; | ||
for (var j = event.steps.length - 1; j >= 0; j--) { | ||
if (event.steps[j].id <= version.lastID) return { event: i, step: j + 1 }; | ||
} | ||
@@ -352,11 +411,11 @@ } | ||
var event = this.events[i]; | ||
for (var j = event.length - 1; j >= 0; j--) { | ||
var step = event[j]; | ||
for (var j = event.steps.length - 1; j >= 0; j--) { | ||
var step = event.steps[j]; | ||
if (step.version <= startVersion) break out; | ||
var off = positions[step.version - startVersion - 1]; | ||
if (off == -1) { | ||
event.splice(j--, 1); | ||
event.steps.splice(j--, 1); | ||
} else { | ||
var inv = rebasedTransform.steps[off].invert(rebasedTransform.docs[off], rebasedTransform.maps[off]); | ||
event[j] = new InvertedStep(inv, startVersion + newMaps.length + off + 1, step.id); | ||
event.steps[j] = new InvertedStep(inv, startVersion + newMaps.length + off + 1, step.id); | ||
} | ||
@@ -405,2 +464,5 @@ } | ||
// Delay between transforms required to compress steps. | ||
var compressDelay = 750; | ||
@@ -426,4 +488,4 @@ | ||
pm.on("transform", function (transform, options) { | ||
return _this3.recordTransform(transform, options); | ||
pm.on("transform", function (transform, selection, options) { | ||
return _this3.recordTransform(transform, selection, options); | ||
}); | ||
@@ -438,3 +500,3 @@ } | ||
key: "recordTransform", | ||
value: function recordTransform(transform, options) { | ||
value: function recordTransform(transform, selection, options) { | ||
if (this.ignoreTransform) return; | ||
@@ -452,3 +514,3 @@ | ||
// Group transforms that occur in quick succession into one event. | ||
if (now > this.lastAddedAt + this.pm.options.historyEventDelay) this.done.newEvent(); | ||
if (now > this.lastAddedAt + this.pm.options.historyEventDelay) this.done.newEvent(selection); | ||
@@ -502,6 +564,8 @@ this.done.addTransform(transform); | ||
var ids = event.ids; | ||
var selection = event.selection; | ||
var selectionBeforeTransform = this.pm.selection; | ||
this.ignoreTransform = true; | ||
this.pm.apply(transform); | ||
this.pm.apply(transform, { selection: selection }); | ||
this.ignoreTransform = false; | ||
@@ -512,3 +576,7 @@ | ||
if (to) { | ||
to.newEvent(); | ||
// Store the selection before transform on the event so that | ||
// it can be reapplied if the event is undone or redone (e.g. | ||
// redoing a character addition should place the cursor after | ||
// the character). | ||
to.newEvent(selectionBeforeTransform); | ||
to.addTransform(transform, ids); | ||
@@ -556,8 +624,10 @@ } | ||
var event = this.done.events[found.event]; | ||
if (found.event == this.done.events.length - 1 && found.step == event.length) return true; | ||
var combined = this.done.events.slice(found.event + 1).reduce(function (comb, arr) { | ||
return comb.concat(arr); | ||
}, event.slice(found.step)); | ||
this.done.events.length = found.event + ((event.length = found.step) ? 1 : 0); | ||
this.done.events.push(combined); | ||
if (found.event == this.done.events.length - 1 && found.step == event.steps.length) return true; | ||
// Combine all steps past the verion to rollback to into | ||
// one event, and then "undo" that event. | ||
var combinedSteps = this.done.events.slice(found.event + 1).reduce(function (comb, arr) { | ||
return comb.concat(arr.steps); | ||
}, event.steps.slice(found.step)); | ||
this.done.events.length = found.event + ((event.steps.length = found.step) ? 1 : 0); | ||
this.done.events.push(new HistoryEvent(combinedSteps, null)); | ||
@@ -580,2 +650,6 @@ this.shift(this.done); | ||
} | ||
// : (Branch) | ||
// Schedule compression for a branch if it needs compressing. | ||
}, { | ||
@@ -582,0 +656,0 @@ key: "maybeScheduleCompressionForBranch", |
@@ -165,2 +165,3 @@ "use strict"; | ||
// menus. | ||
if (!(0, _selection.hasFocus)(pm)) return; | ||
pm.signal("interaction"); | ||
@@ -190,3 +191,3 @@ if (e.keyCode == 16) pm.input.shiftKey = true; | ||
handlers.keypress = function (pm, e) { | ||
if (pm.input.composing || !e.charCode || e.ctrlKey && !e.altKey || _dom.browser.mac && e.metaKey) return; | ||
if (!(0, _selection.hasFocus)(pm) || pm.input.composing || !e.charCode || e.ctrlKey && !e.altKey || _dom.browser.mac && e.metaKey) return; | ||
if (dispatchKey(pm, _browserkeymap2.default.keyName(e), e)) return; | ||
@@ -247,3 +248,3 @@ var sel = pm.selection; | ||
if (tripleClick) handleTripleClick(pm, e);else pm.input.mouseDown = new MouseDown(pm, e, doubleClick); | ||
if (tripleClick) handleTripleClick(pm, e);else if (doubleClick && (0, _dompos.handleNodeClick)(pm, "handleDoubleClick", e, true)) {} else pm.input.mouseDown = new MouseDown(pm, e, doubleClick); | ||
}; | ||
@@ -286,11 +287,11 @@ | ||
key: "up", | ||
value: function up() { | ||
value: function up(event) { | ||
this.done(); | ||
if (this.leaveToBrowser) { | ||
if (this.leaveToBrowser || !(0, _dom.contains)(this.pm.content, event.target)) { | ||
this.pm.sel.fastPoll(); | ||
} else if (this.event.ctrlKey) { | ||
selectClickedNode(this.pm, this.event); | ||
} else if (!(0, _dompos.handleNodeClick)(this.pm, "handleClick", this.event, true)) { | ||
var pos = (0, _dompos.selectableNodeAbove)(this.pm, this.event.target, { left: this.x, top: this.y }); | ||
selectClickedNode(this.pm, event); | ||
} else if (!(0, _dompos.handleNodeClick)(this.pm, "handleClick", event, true)) { | ||
var pos = (0, _dompos.selectableNodeAbove)(this.pm, event.target, { left: this.x, top: this.y }); | ||
if (pos) { | ||
@@ -343,3 +344,3 @@ this.pm.setNodeSelection(pos); | ||
handlers.compositionstart = function (pm, e) { | ||
if (pm.input.maybeAbortComposition()) return; | ||
if (!(0, _selection.hasFocus)(pm) || pm.input.maybeAbortComposition()) return; | ||
@@ -353,2 +354,3 @@ pm.flush(); | ||
handlers.compositionupdate = function (pm, e) { | ||
if (!(0, _selection.hasFocus)(pm)) return; | ||
var info = pm.input.composing; | ||
@@ -365,2 +367,3 @@ if (info && info.data != e.data) { | ||
handlers.compositionend = function (pm, e) { | ||
if (!(0, _selection.hasFocus)(pm)) return; | ||
var info = pm.input.composing; | ||
@@ -386,3 +389,4 @@ if (info) { | ||
handlers.input = function (pm) { | ||
handlers.input = function (pm, e) { | ||
if (!(0, _selection.hasFocus)(pm)) return; | ||
if (pm.input.skipInput) return --pm.input.skipInput; | ||
@@ -396,3 +400,6 @@ | ||
pm.startOperation({ readSelection: false }); | ||
(0, _domchange.applyDOMChange)(pm); | ||
var change = (0, _domchange.readDOMChange)(pm); | ||
if (change) { | ||
if (change.type == "enter") dispatchKey(pm, "Enter", e);else change.run(); | ||
} | ||
pm.scrollIntoView(); | ||
@@ -508,2 +515,3 @@ }; | ||
handlers.paste = function (pm, e) { | ||
if (!(0, _selection.hasFocus)(pm)) return; | ||
if (!e.clipboardData) return; | ||
@@ -510,0 +518,0 @@ var sel = pm.selection; |
@@ -276,2 +276,7 @@ "use strict"; | ||
// | ||
// **`filter`**: ?bool | ||
// : When set to false, suppresses the ability of the | ||
// [`"filterTransform"` event](#ProseMirror.event_beforeTransform) | ||
// to cancel this transform. | ||
// | ||
// Returns the transform, or `false` if there were no steps in it. | ||
@@ -286,2 +291,10 @@ // | ||
// :: (transform: Transform) #path=ProseMirror#events#filterTransform | ||
// Fired before a transform (applied without `filter: false`) is | ||
// applied. The handler can return a truthy value to cancel the | ||
// transform. | ||
if (options.filter !== false && this.signalHandleable("filterTransform", transform)) return false; | ||
var selectionBeforeTransform = this.selection; | ||
// :: (transform: Transform, options: Object) #path=ProseMirror#events#beforeTransform | ||
@@ -294,7 +307,8 @@ // Indicates that the given transform is about to be | ||
this.updateDoc(transform.doc, transform, options.selection); | ||
// :: (transfom: Transform, options: Object) #path=ProseMirror#events#transform | ||
// :: (transform: Transform, selectionBeforeTransform: Selection, options: Object) #path=ProseMirror#events#transform | ||
// Signals that a (non-empty) transformation has been aplied to | ||
// the editor. Passes the `Transform` and the options given to | ||
// [`apply`](#ProseMirror.apply) as arguments to the handler. | ||
this.signal("transform", transform, options); | ||
// the editor. Passes the `Transform`, the selection before the | ||
// transform, and the options given to [`apply`](#ProseMirror.apply) | ||
// as arguments to the handler. | ||
this.signal("transform", transform, selectionBeforeTransform, options); | ||
if (options.scrollIntoView) this.scrollIntoView(); | ||
@@ -635,3 +649,14 @@ return transform; | ||
} | ||
// :: (string) → string | ||
// Return a translated string, if a translate function has been supplied, | ||
// or the original string. | ||
}, { | ||
key: "translate", | ||
value: function translate(string) { | ||
var trans = this.options.translate; | ||
return trans ? trans(string) : string; | ||
} | ||
}, { | ||
key: "selection", | ||
@@ -638,0 +663,0 @@ get: function get() { |
@@ -21,2 +21,6 @@ "use strict"; | ||
// An option encapsulates functionality for an editor instance, | ||
// e.g. the amount of history events that the editor should hold | ||
// onto or the document's schema. | ||
var Option = function Option(defaultValue, update, updateOnInit) { | ||
@@ -26,2 +30,5 @@ _classCallCheck(this, Option); | ||
this.defaultValue = defaultValue; | ||
// A function that will be invoked with the option's old and new | ||
// value every time the option is [set](#ProseMirror.setOption). | ||
// This function should bootstrap option functionality. | ||
this.update = update; | ||
@@ -94,2 +101,8 @@ this.updateOnInit = updateOnInit !== false; | ||
// :: ?(string) → string #path=translate #kind=option | ||
// Optional function to translate strings such as menu labels and prompts. | ||
// When set, should be a function that takes a string as argument and returns | ||
// a string, i.e. :: (string) → string | ||
defineOption("translate", null); // FIXME create a way to explicitly force a menu redraw | ||
function parseOptions(obj) { | ||
@@ -96,0 +109,0 @@ var result = Object.create(null); |
@@ -177,3 +177,3 @@ "use strict"; | ||
// | ||
// **Keybindings:** Alt-Right '*', Alt-Right '-' | ||
// **Keybindings:** Shift-Mod-8 | ||
_model.BulletList.register("command", "wrap", { | ||
@@ -190,3 +190,3 @@ derive: { list: true }, | ||
}, | ||
keys: ["Alt-Right '*'", "Alt-Right '-'"] | ||
keys: ["Shift-Mod-8"] | ||
}); | ||
@@ -197,3 +197,3 @@ | ||
// | ||
// **Keybindings:** Alt-Right '1' | ||
// **Keybindings:** Shift-Mod-8 | ||
_model.OrderedList.register("command", "wrap", { | ||
@@ -210,3 +210,3 @@ derive: { list: true }, | ||
}, | ||
keys: ["Alt-Right '1'"] | ||
keys: ["Shift-Mod-9"] | ||
}); | ||
@@ -217,3 +217,3 @@ | ||
// | ||
// **Keybindings:** Alt-Right '>', Alt-Right '"' | ||
// **Keybindings:** Shift-Mod-. | ||
_model.BlockQuote.register("command", "wrap", { | ||
@@ -230,3 +230,3 @@ derive: true, | ||
}, | ||
keys: ["Alt-Right '>'", "Alt-Right '\"'"] | ||
keys: ["Shift-Mod-."] | ||
}); | ||
@@ -283,3 +283,3 @@ | ||
// | ||
// **Keybindings:** Mod-1 through Mod-6 | ||
// **Keybindings:** Shift-Mod-1 through Shift-Mod-6 | ||
_model.Heading.registerComputed("command", "make" + i, function (type) { | ||
@@ -289,3 +289,3 @@ if (i <= type.maxLevel) return { | ||
label: "Change to heading " + i, | ||
keys: i < 10 && ["Mod-" + i], | ||
keys: i <= 6 && ["Shift-Mod-" + i], | ||
menu: { | ||
@@ -305,7 +305,7 @@ group: "textblockHeading", rank: 30 + i, | ||
// | ||
// **Keybindings:** Mod-0 | ||
// **Keybindings:** Shift-Mod-0 | ||
_model.Paragraph.register("command", "make", { | ||
derive: true, | ||
label: "Change to paragraph", | ||
keys: ["Mod-0"], | ||
keys: ["Shift-Mod-0"], | ||
menu: { | ||
@@ -321,7 +321,7 @@ group: "textblock", rank: 10, | ||
// | ||
// **Keybindings:** Mod-\ | ||
// **Keybindings:** Shift-Mod-\ | ||
_model.CodeBlock.register("command", "make", { | ||
derive: true, | ||
label: "Change to code block", | ||
keys: ["Mod-\\"], | ||
keys: ["Shift-Mod-\\"], | ||
menu: { | ||
@@ -328,0 +328,0 @@ group: "textblock", rank: 20, |
@@ -164,3 +164,10 @@ "use strict"; | ||
var newRange = findSelectionNear(doc, head, this.range.head && this.range.head.cmp(head) < 0 ? -1 : 1); | ||
if (newRange instanceof TextSelection && doc.path(anchor.path).isTextblock) newRange = new TextSelection(anchor, newRange.head); | ||
if (newRange instanceof TextSelection && doc.path(anchor.path).isTextblock) { | ||
newRange = new TextSelection(anchor, newRange.head); | ||
} else if (newRange instanceof NodeSelection && (anchor.cmp(newRange.from) < 0 || anchor.cmp(newRange.to) > 0)) { | ||
// If head falls on a node, but anchor falls outside of it, | ||
// create a text selection between them | ||
var inv = anchor.cmp(newRange.to) > 0; | ||
newRange = new TextSelection(findSelectionNear(doc, anchor, inv ? -1 : 1, true).anchor, findSelectionNear(doc, inv ? newRange.from : newRange.to, inv ? 1 : -1, true).head); | ||
} | ||
this.setAndSignal(newRange); | ||
@@ -269,6 +276,6 @@ | ||
// :: Pos #path=Selection.prototype.from | ||
// The start of the selection. | ||
// The left-bound of the selection. | ||
// :: Pos #path=Selection.prototype.to | ||
// The end of the selection. | ||
// The right-bound of the selection. | ||
@@ -413,2 +420,3 @@ // :: bool #path=Selection.empty | ||
function hasFocus(pm) { | ||
if (document.activeElement != pm.content) return false; | ||
var sel = window.getSelection(); | ||
@@ -418,2 +426,4 @@ return sel.rangeCount && (0, _dom.contains)(pm.content, sel.anchorNode); | ||
// Try to find a selection inside the node at the given path coming | ||
// from a given direction. | ||
function findSelectionIn(doc, path, offset, dir, text) { | ||
@@ -423,2 +433,4 @@ var node = doc.path(path); | ||
// Iterate over child nodes recursively coming from the given | ||
// direction and return the first viable selection. | ||
for (var i = offset + (dir > 0 ? 0 : -1); dir > 0 ? i < node.size : i >= 0; i += dir) { | ||
@@ -436,2 +448,6 @@ var child = node.child(i); | ||
// Create a selection which is moved relative to a position in a | ||
// given direction. When a selection isn't found at the given position, | ||
// walks up the document tree one level and one step in the | ||
// desired direction. | ||
function findSelectionFrom(doc, pos, dir, text) { | ||
@@ -455,2 +471,3 @@ for (var path = pos.path.slice(), offset = pos.offset;;) { | ||
// Find the selection closes to the start of the given node. | ||
function findSelectionAtStart(node) { | ||
@@ -463,2 +480,3 @@ var path = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; | ||
// Find the selection closes to the end of the given node. | ||
function findSelectionAtEnd(node) { | ||
@@ -471,2 +489,5 @@ var path = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; | ||
// : (ProseMirror, Pos, number) | ||
// Whether vertical position motion in a given direction | ||
// from a position would leave a text block. | ||
function verticalMotionLeavesTextblock(pm, pos, dir) { | ||
@@ -473,0 +494,0 @@ var dom = (0, _dompos.pathToDOM)(pm.content, pos.path); |
@@ -235,3 +235,3 @@ "use strict"; | ||
def(_model.HorizontalRule, function (_, s) { | ||
return s.elt("hr"); | ||
return s.elt("div", null, s.elt("hr")); | ||
}); | ||
@@ -238,0 +238,0 @@ |
@@ -168,3 +168,3 @@ "use strict"; | ||
for (var i = parent.iter(0, pos.offset), child; child = i.next().value;) { | ||
if (child.isText) textBefore += child.text;else textBefore = ""; | ||
if (child.isText) textBefore += child.text;else textBefore += ""; | ||
if (i.atEnd() && child.marks.some(function (st) { | ||
@@ -171,0 +171,0 @@ return st.type.isCode; |
@@ -171,30 +171,44 @@ "use strict"; | ||
var stack = []; | ||
var active = []; | ||
var progress = function progress(node) { | ||
var marks = node ? node.marks.slice() : []; | ||
if (stack.length && stack[stack.length - 1].type == "code" && (!marks.length || marks[marks.length - 1].type != "code")) { | ||
_this2.text("`", false); | ||
stack.pop(); | ||
} | ||
for (var j = 0; j < stack.length; j++) { | ||
var cur = stack[j], | ||
found = false; | ||
for (var k = 0; k < marks.length; k++) { | ||
if (marks[k].eq(stack[j])) { | ||
marks.splice(k, 1); | ||
found = true; | ||
break; | ||
var marks = node ? node.marks : []; | ||
var code = marks.length && marks[marks.length - 1].type.isCode && marks[marks.length - 1]; | ||
var len = marks.length - (code ? 1 : 0); | ||
// Try to reorder 'mixable' marks, such as em and strong, which | ||
// in Markdown may be opened and closed in different order, so | ||
// that order of the marks for the token matches the order in | ||
// active. | ||
outer: for (var i = 0; i < len; i++) { | ||
var mark = marks[i]; | ||
if (!mark.type.markdownMixable) break; | ||
for (var j = 0; j < active.length; j++) { | ||
var other = active[j]; | ||
if (!other.type.markdownMixable) break; | ||
if (mark.eq(other)) { | ||
if (i > j) marks = marks.slice(0, j).concat(mark).concat(marks.slice(j, i)).concat(marks.slice(i + 1), len);else if (j > i) marks = marks.slice(0, i).concat(marks.slice(i + 1, j)).concat(mark).concat(marks.slice(j, len)); | ||
continue outer; | ||
} | ||
} | ||
if (!found) { | ||
_this2.text(_this2.markString(cur, false), false); | ||
stack.splice(j--, 1); | ||
} | ||
} | ||
for (var j = 0; j < marks.length; j++) { | ||
var cur = marks[j]; | ||
stack.push(cur); | ||
_this2.text(_this2.markString(cur, true), false); | ||
// Find the prefix of the mark set that didn't change | ||
var keep = 0; | ||
while (keep < Math.min(active.length, len) && marks[keep].eq(active[keep])) { | ||
++keep; | ||
} // Close the marks that need to be closed | ||
while (keep < active.length) { | ||
_this2.text(_this2.markString(active.pop(), false), false); | ||
} // Open the marks that need to be opened | ||
while (active.length < len) { | ||
var add = marks[active.length]; | ||
active.push(add); | ||
_this2.text(_this2.markString(add, true), false); | ||
} | ||
if (node) _this2.render(node); | ||
// Render the node. Special case code marks, since their content | ||
// may not be escaped. | ||
if (node) { | ||
if (code && node.isText) _this2.text(_this2.markString(code, false) + node.text + _this2.markString(code, true), false);else _this2.render(node); | ||
} | ||
}; | ||
@@ -257,2 +271,6 @@ parent.forEach(progress); | ||
} | ||
// : (Mark, bool) → string | ||
// Get the markdown string for a given opening or closing mark. | ||
}, { | ||
@@ -345,15 +363,13 @@ key: "markString", | ||
function defMark(mark, open, close) { | ||
mark.prototype.openMarkdown = open; | ||
mark.prototype.closeMarkdown = close; | ||
} | ||
_model.EmMark.prototype.openMarkdown = _model.EmMark.prototype.closeMarkdown = "*"; | ||
_model.EmMark.prototype.markdownMixable = true; | ||
defMark(_model.EmMark, "*", "*"); | ||
_model.StrongMark.prototype.openMarkdown = _model.StrongMark.prototype.closeMarkdown = "**"; | ||
_model.StrongMark.prototype.markdownMixable = true; | ||
defMark(_model.StrongMark, "**", "**"); | ||
defMark(_model.LinkMark, "[", function (state, mark) { | ||
_model.LinkMark.prototype.openMarkdown = "["; | ||
_model.LinkMark.prototype.closeMarkdown = function (state, mark) { | ||
return "](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")"; | ||
}); | ||
}; | ||
defMark(_model.CodeMark, "`", "`"); | ||
_model.CodeMark.prototype.openMarkdown = _model.CodeMark.prototype.closeMarkdown = "`"; |
@@ -53,4 +53,5 @@ "use strict"; | ||
if (!command.label) return null; | ||
var label = pm.translate(command.label); | ||
var key = command.name && pm.keyForCommand(command.name); | ||
return key ? command.label + " (" + key + ")" : command.label; | ||
return key ? label + " (" + key + ")" : label; | ||
} | ||
@@ -80,3 +81,6 @@ | ||
} | ||
}, { | ||
key: "render", | ||
// :: (ProseMirror) → DOMNode | ||
@@ -86,5 +90,2 @@ // Renders the command according to its [display | ||
// executes the command when the representation is clicked. | ||
}, { | ||
key: "render", | ||
value: function render(pm) { | ||
@@ -108,3 +109,4 @@ var cmd = this.command(pm), | ||
} else if (disp.type == "label") { | ||
dom = (0, _dom.elt)("div", null, disp.label || cmd.spec.label); | ||
var label = pm.translate(disp.label || cmd.spec.label); | ||
dom = (0, _dom.elt)("div", null, label); | ||
} else { | ||
@@ -122,4 +124,10 @@ throw new _error.AssertionError("Unsupported command display style: " + disp.type); | ||
}); | ||
dom.setAttribute("data-command", this.commandName); | ||
return dom; | ||
} | ||
}, { | ||
key: "commandName", | ||
get: function get() { | ||
return typeof this.command_ === "string" ? this.command_.command : this.command_.name; | ||
} | ||
}]); | ||
@@ -239,2 +247,3 @@ | ||
var label = this.options.activeLabel && this.findActiveIn(this, pm) || this.options.label; | ||
label = pm.translate(label); | ||
var dom = (0, _dom.elt)("div", { class: prefix + "-dropdown " + (this.options.class || ""), | ||
@@ -335,3 +344,3 @@ style: this.options.css, | ||
var label = (0, _dom.elt)("div", { class: prefix + "-submenu-label" }, this.options.label); | ||
var label = (0, _dom.elt)("div", { class: prefix + "-submenu-label" }, pm.translate(this.options.label)); | ||
var wrap = (0, _dom.elt)("div", { class: prefix + "-submenu-wrap" }, label, (0, _dom.elt)("div", { class: prefix + "-submenu" }, items)); | ||
@@ -338,0 +347,0 @@ label.addEventListener("mousedown", function (e) { |
@@ -76,3 +76,3 @@ "use strict"; | ||
this.selectedBlockMenu = this.config.selectedBlockMenu; | ||
this.updater = new _update.UpdateScheduler(pm, "change selectionChange blur commandsChanged", function () { | ||
this.updater = new _update.UpdateScheduler(pm, "change selectionChange blur focus commandsChanged", function () { | ||
return _this.update(); | ||
@@ -79,0 +79,0 @@ }); |
@@ -319,3 +319,3 @@ "use strict"; | ||
get: function get() { | ||
return 51; | ||
return 31; | ||
} | ||
@@ -342,3 +342,3 @@ }]); | ||
get: function get() { | ||
return 52; | ||
return 32; | ||
} | ||
@@ -376,3 +376,3 @@ }]); | ||
get: function get() { | ||
return 25; | ||
return 60; | ||
} | ||
@@ -379,0 +379,0 @@ }]); |
@@ -507,3 +507,4 @@ "use strict"; | ||
if (end > this.offset) { | ||
var sliceEnd = node.width; | ||
var sliceEnd = node.width, | ||
sliceStart = this.offset - offset; | ||
if (end > this.endOffset) { | ||
@@ -513,3 +514,3 @@ sliceEnd = this.endOffset - offset; | ||
} | ||
node = node.copy(node.text.slice(this.offset - offset, sliceEnd)); | ||
node = sliceEnd > sliceStart ? node.copy(node.text.slice(this.offset - offset, sliceEnd)) : null; | ||
this.offset = end; | ||
@@ -516,0 +517,0 @@ return node; |
@@ -18,2 +18,4 @@ "use strict"; | ||
var _schema = require("./schema"); | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
@@ -560,7 +562,8 @@ | ||
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(TextNode).call(this, type, attrs, null, marks)); | ||
if (!content) throw new _schema.SchemaError("Empty text nodes are not allowed"); | ||
// :: ?string | ||
// For text nodes, this contains the node's text content. | ||
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(TextNode).call(this, type, attrs, null, marks)); | ||
_this.text = content; | ||
@@ -567,0 +570,0 @@ return _this; |
@@ -453,9 +453,10 @@ "use strict"; | ||
var NodeKind = exports.NodeKind = function () { | ||
// :: (string, [NodeKind]) | ||
// :: (string, ?[NodeKind], ?[NodeKind]) | ||
// Create a new node kind with the given set of superkinds (the new | ||
// kind counts as a member of each of the superkinds). The `name` | ||
// field is only for debugging purposes—kind equivalens is defined | ||
// by identity. | ||
// kind counts as a member of each of the superkinds) and subkinds | ||
// (which will count as a member of this new kind). The `name` field | ||
// is only for debugging purposes—kind equivalens is defined by | ||
// identity. | ||
function NodeKind(name) { | ||
function NodeKind(name, supers, subs) { | ||
var _this4 = this; | ||
@@ -466,23 +467,45 @@ | ||
this.name = name; | ||
// FIXME temporary backwards-compatibility kludge | ||
if (supers && supers instanceof NodeKind) { | ||
supers = Array.prototype.slice.call(arguments, 1); | ||
subs = null; | ||
} | ||
this.id = ++NodeKind.nextID; | ||
this.supers = Object.create(null); | ||
this.id = ++NodeKind.nextID; | ||
this.supers[this.id] = true; | ||
this.subs = subs || []; | ||
for (var _len = arguments.length, supers = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
supers[_key - 1] = arguments[_key]; | ||
} | ||
if (supers) supers.forEach(function (sup) { | ||
return _this4.addSuper(sup); | ||
}); | ||
if (subs) subs.forEach(function (sub) { | ||
return _this4.addSub(sub); | ||
}); | ||
} | ||
supers.forEach(function (sup) { | ||
_createClass(NodeKind, [{ | ||
key: "addSuper", | ||
value: function addSuper(sup) { | ||
for (var id in sup.supers) { | ||
_this4.supers[id] = true; | ||
this.supers[id] = true; | ||
sup.subs.push(this); | ||
} | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "addSub", | ||
value: function addSub(sub) { | ||
var _this5 = this; | ||
// :: (NodeKind) → bool | ||
// Test whether `other` is a subkind of this kind (or the same | ||
// kind). | ||
if (this.supers[sub.id]) throw new SchemaError("Circular subkind relation"); | ||
sub.supers[this.id] = true; | ||
sub.subs.forEach(function (next) { | ||
return _this5.addSub(next); | ||
}); | ||
} | ||
// :: (NodeKind) → bool | ||
// Test whether `other` is a subkind of this kind (or the same | ||
// kind). | ||
_createClass(NodeKind, [{ | ||
}, { | ||
key: "isSubKind", | ||
@@ -507,3 +530,3 @@ value: function isSubKind(other) { | ||
// `NodeKind.inline`. | ||
NodeKind.text = new NodeKind("text", NodeKind.inline); | ||
NodeKind.text = new NodeKind("text", [NodeKind.inline]); | ||
@@ -710,13 +733,13 @@ // ;; Base type for block nodetypes. | ||
var _this9 = _possibleConstructorReturn(this, Object.getPrototypeOf(MarkType).call(this)); | ||
var _this10 = _possibleConstructorReturn(this, Object.getPrototypeOf(MarkType).call(this)); | ||
_this9.name = name; | ||
_this9.freezeAttrs(); | ||
_this9.rank = rank; | ||
_this10.name = name; | ||
_this10.freezeAttrs(); | ||
_this10.rank = rank; | ||
// :: Schema | ||
// The schema that this mark type instance is part of. | ||
_this9.schema = schema; | ||
var defaults = _this9.getDefaultAttrs(); | ||
_this9.instance = defaults && new _mark.Mark(_this9, defaults); | ||
return _this9; | ||
_this10.schema = schema; | ||
var defaults = _this10.getDefaultAttrs(); | ||
_this10.instance = defaults && new _mark.Mark(_this10, defaults); | ||
return _this10; | ||
} | ||
@@ -913,3 +936,4 @@ | ||
// :: (string, ?[Mark]) → Node | ||
// Create a text node in the schema. This method is bound to the Schema. | ||
// Create a text node in the schema. This method is bound to the | ||
// Schema. Empty text nodes are not allowed. | ||
@@ -916,0 +940,0 @@ }, { |
@@ -152,3 +152,3 @@ "use strict"; | ||
type(pm1, "E"); | ||
conv(pm1, pm2, "ACDE"); | ||
conv(pm1, pm2, "ADCE"); | ||
}); | ||
@@ -155,0 +155,0 @@ |
@@ -62,2 +62,3 @@ "use strict"; | ||
test("joinBackward", (0, _build.doc)(_build.hr, (0, _build.p)("<a>there")), (0, _build.doc)((0, _build.p)("there"))); | ||
test("joinBackward", (0, _build.doc)(_build.hr, (0, _build.p)("<a>"), _build.hr), (0, _build.doc)(_build.hr, _build.hr)); | ||
test("joinBackward", (0, _build.doc)(_build.hr, (0, _build.blockquote)((0, _build.p)("<a>there"))), (0, _build.doc)((0, _build.blockquote)((0, _build.p)("there")))); | ||
@@ -64,0 +65,0 @@ test("joinBackward", (0, _build.doc)((0, _build.p)("<a>foo")), (0, _build.doc)((0, _build.p)("foo"))); |
@@ -15,5 +15,9 @@ "use strict"; | ||
function apply(pm) { | ||
(0, _domchange.readDOMChange)(pm).run(); | ||
} | ||
test("add_text", function (pm) { | ||
(0, _testSelection.findTextNode)(pm.content, "hello").nodeValue = "heLllo"; | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("heLllo"))); | ||
@@ -24,3 +28,3 @@ }); | ||
(0, _testSelection.findTextNode)(pm.content, "hello").nodeValue = "heo"; | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("heo"))); | ||
@@ -32,3 +36,3 @@ }); | ||
txt.parentNode.appendChild(document.createTextNode("!")); | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("hello!"))); | ||
@@ -40,3 +44,3 @@ }); | ||
txt.parentNode.appendChild(document.createElement("em")).appendChild(document.createTextNode("!")); | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("hello", (0, _build.em)("!")))); | ||
@@ -48,3 +52,3 @@ }); | ||
txt.parentNode.removeChild(txt); | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)())); | ||
@@ -55,3 +59,3 @@ }); | ||
pm.content.insertBefore(document.createElement("p"), pm.content.firstChild).appendChild(document.createTextNode("hey")); | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("hey"), (0, _build.p)("hello"))); | ||
@@ -62,3 +66,3 @@ }); | ||
pm.content.insertBefore(document.createElement("p"), pm.content.firstChild).appendChild(document.createTextNode("hello")); | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("hello"), (0, _build.p)("hello"))); | ||
@@ -69,4 +73,11 @@ }); | ||
(0, _testSelection.findTextNode)(pm.content, "hello").nodeValue = "helhello"; | ||
(0, _domchange.applyDOMChange)(pm); | ||
apply(pm); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("helhello"))); | ||
}); | ||
test("detect_enter", function (pm) { | ||
(0, _testSelection.findTextNode)(pm.content, "hello").nodeValue = "hel"; | ||
pm.content.appendChild(document.createElement("p")).innerHTML = "lo"; | ||
var change = (0, _domchange.readDOMChange)(pm); | ||
(0, _cmp.cmp)(change && change.type, "enter"); | ||
}); |
@@ -15,3 +15,3 @@ "use strict"; | ||
function cut(pm) { | ||
function cutHistory(pm) { | ||
pm.history.lastAddedAt = 0; | ||
@@ -39,3 +39,3 @@ } | ||
type(pm, "a"); | ||
cut(pm); | ||
cutHistory(pm); | ||
type(pm, "b"); | ||
@@ -65,3 +65,3 @@ (0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("ab"))); | ||
type(pm, "hello"); | ||
cut(pm); | ||
cutHistory(pm); | ||
type(pm, "!"); | ||
@@ -80,3 +80,3 @@ pm.tr.insertText((0, _cmp.P)(0, 0), "....").apply(); | ||
type(pm, "hello"); | ||
cut(pm); | ||
cutHistory(pm); | ||
pm.tr.delete((0, _cmp.P)(0, 0), (0, _cmp.P)(0, 5)).apply(); | ||
@@ -93,3 +93,3 @@ (0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)())); | ||
type(pm, "hello"); | ||
cut(pm); | ||
cutHistory(pm); | ||
pm.tr.delete((0, _cmp.P)(0, 0), (0, _cmp.P)(0, 5)).apply(); | ||
@@ -105,3 +105,3 @@ (0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)())); | ||
type(pm, "hi"); | ||
cut(pm); | ||
cutHistory(pm); | ||
type(pm, "hello"); | ||
@@ -117,6 +117,6 @@ pm.tr.delete((0, _cmp.P)(0, 0), (0, _cmp.P)(0, 7)).apply({ addToHistory: false }); | ||
type(pm, " two"); | ||
cut(pm); | ||
cutHistory(pm); | ||
type(pm, " three"); | ||
pm.tr.insertText((0, _cmp.P)(0, 0), "zero ").apply(); | ||
cut(pm); | ||
cutHistory(pm); | ||
pm.tr.split((0, _cmp.P)(0, 0)).apply(); | ||
@@ -136,7 +136,7 @@ pm.setTextSelection((0, _cmp.P)(0, 0)); | ||
type(pm, " two"); | ||
cut(pm); | ||
cutHistory(pm); | ||
pm.tr.insertText(pm.selection.head, "xxx").apply({ addToHistory: false }); | ||
type(pm, " three"); | ||
pm.tr.insertText((0, _cmp.P)(0, 0), "zero ").apply(); | ||
cut(pm); | ||
cutHistory(pm); | ||
pm.tr.split((0, _cmp.P)(0, 0)).apply(); | ||
@@ -158,3 +158,3 @@ pm.setTextSelection((0, _cmp.P)(0, 0)); | ||
pm.setTextSelection((0, _cmp.P)(0, 1)); | ||
cut(pm); | ||
cutHistory(pm); | ||
type(pm, "one"); | ||
@@ -168,4 +168,6 @@ type(pm, "two"); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("XY!"))); | ||
(0, _cmp.cmpStr)(pm.selection.anchor, (0, _cmp.P)(0, 1)); | ||
pm.execCommand("redo"); | ||
(0, _cmp.cmpNode)(pm.doc, (0, _build.doc)((0, _build.p)("XonetwothreeY!"))); | ||
(0, _cmp.cmpStr)(pm.selection.anchor, (0, _cmp.P)(0, 12)); | ||
}); | ||
@@ -182,3 +184,3 @@ | ||
type(pm, "hello"); | ||
cut(pm); | ||
cutHistory(pm); | ||
var version = pm.history.getVersion(); | ||
@@ -199,3 +201,3 @@ type(pm, "ok"); | ||
type(pm, "ok"); | ||
cut(pm); | ||
cutHistory(pm); | ||
type(pm, "more"); | ||
@@ -208,2 +210,25 @@ (0, _cmp.is)(pm.history.backToVersion(version), "rollback"); | ||
(0, _cmp.is)(!pm.history.backToVersion(version), "failed rollback"); | ||
}); | ||
test("setSelectionOnUndo", function (pm) { | ||
type(pm, "hi"); | ||
cutHistory(pm); | ||
pm.setTextSelection((0, _cmp.P)(0, 0), (0, _cmp.P)(0, 2)); | ||
var selection = pm.selection; | ||
pm.tr.replaceWith(selection.from, selection.to, pm.schema.text("hello")).apply(); | ||
var selection2 = pm.selection; | ||
pm.execCommand("undo"); | ||
(0, _cmp.is)(pm.selection.eq(selection), "failed restoring selection after undo"); | ||
pm.execCommand("redo"); | ||
(0, _cmp.is)(pm.selection.eq(selection2), "failed restoring selection after redo"); | ||
}); | ||
test("rebaseSelectionOnUndo", function (pm) { | ||
type(pm, "hi"); | ||
cutHistory(pm); | ||
pm.setTextSelection((0, _cmp.P)(0, 0), (0, _cmp.P)(0, 2)); | ||
pm.tr.insert((0, _cmp.P)(0, 0), pm.schema.text("hello")).apply(); | ||
pm.tr.insert((0, _cmp.P)(0, 0), pm.schema.text("---")).apply({ addToHistory: false }); | ||
pm.execCommand("undo"); | ||
(0, _cmp.cmpStr)(pm.selection.head, (0, _cmp.P)(0, 5)); | ||
}); |
@@ -11,2 +11,5 @@ "use strict"; | ||
// : (string) → Function | ||
// Create a function for creating inline nodes with brevity. | ||
// For use with `doc()`. | ||
function buildInline(style) { | ||
@@ -18,2 +21,5 @@ return function () { | ||
// : (string, Object) → Function | ||
// Create a function which creating block nodes with brevity. | ||
// For use with `doc()`. | ||
function build(type, attrs) { | ||
@@ -68,2 +74,5 @@ return function () { | ||
// : (...nodes: Node) → Doc | ||
// Create a document node. Child nodes can be added with | ||
// abbreviated node notation, see `build()`. | ||
function doc() { | ||
@@ -70,0 +79,0 @@ var content = []; |
@@ -40,4 +40,8 @@ "use strict"; | ||
t("inline_overlap", "This is **strong *emphasized text with `code` in* it**", (0, _build.doc)((0, _build.p)("This is ", (0, _build.strong)("strong ", (0, _build.em)("emphasized text with ", (0, _build.code)("code"), " in"), " it")))); | ||
t("inline_overlap_mix", "This is **strong *emphasized text with `code` in* it**", (0, _build.doc)((0, _build.p)("This is ", (0, _build.strong)("strong ", (0, _build.em)("emphasized text with ", (0, _build.code)("code"), " in"), " it")))); | ||
t("inline_overlap_link", "**[link](http://foo) is bold**", (0, _build.doc)((0, _build.p)((0, _build.strong)((0, _build.a)("link"), " is bold")))); | ||
t("inline_overlap_code", "**`code` is bold**", (0, _build.doc)((0, _build.p)((0, _build.strong)((0, _build.code)("code"), " is bold")))); | ||
t("link", "My [link](http://foo) goes to foo", (0, _build.doc)((0, _build.p)("My ", (0, _build.a)("link"), " goes to foo"))); | ||
@@ -44,0 +48,0 @@ |
@@ -10,2 +10,6 @@ "use strict"; | ||
// :(string, Function) | ||
// Define a test. A test should include a descriptive name and | ||
// a function which runs the test. If a test fails, it should | ||
// throw a Failure. | ||
function defTest(name, f) { | ||
@@ -12,0 +16,0 @@ if (name in tests) throw new Error("Duplicate definition of test " + name); |
@@ -23,3 +23,3 @@ "use strict"; | ||
return new _step.StepResult((0, _tree.copyStructure)(doc, step.from, step.to, function (node, from, to) { | ||
if (!node.type.canContainMark(step.param)) return node; | ||
if (!node.type.canContainMark(step.param.type)) return node; | ||
return (0, _tree.copyInline)(node, from, to, function (node) { | ||
@@ -26,0 +26,0 @@ return node.mark(step.param.addToSet(node.marks)); |
@@ -53,7 +53,13 @@ "use strict"; | ||
}); | ||
var promptTitle = (0, _dom.elt)("h5", {}, command.spec && command.spec.label ? pm.translate(command.spec.label) : ""); | ||
var submitButton = (0, _dom.elt)("button", { type: "submit", class: "ProseMirror-prompt-submit" }, "Ok"); | ||
var cancelButton = (0, _dom.elt)("button", { type: "button", class: "ProseMirror-prompt-cancel" }, "Cancel"); | ||
cancelButton.addEventListener("click", function () { | ||
return _this.close(); | ||
}); | ||
// :: DOMNode | ||
// An HTML form wrapping the fields. | ||
this.form = (0, _dom.elt)("form", null, this.fields.map(function (f) { | ||
this.form = (0, _dom.elt)("form", null, promptTitle, this.fields.map(function (f) { | ||
return (0, _dom.elt)("div", null, f); | ||
})); | ||
}), (0, _dom.elt)("div", { class: "ProseMirror-prompt-buttons" }, submitButton, " ", cancelButton)); | ||
} | ||
@@ -234,3 +240,3 @@ | ||
return (0, _dom.elt)("input", { type: "text", | ||
placeholder: param.label, | ||
placeholder: this.translate(param.label), | ||
value: value, | ||
@@ -246,5 +252,7 @@ autocomplete: "off" }); | ||
render: function render(param, value) { | ||
var _this4 = this; | ||
var options = param.options.call ? param.options(this) : param.options; | ||
return (0, _dom.elt)("select", null, options.map(function (o) { | ||
return (0, _dom.elt)("option", { value: o.value, selected: o.value == value ? "true" : null }, o.label); | ||
return (0, _dom.elt)("option", { value: o.value, selected: o.value == value ? "true" : null }, _this4.translate(o.label)); | ||
})); | ||
@@ -277,3 +285,3 @@ }, | ||
wrapper.style.left = options.pos.left - outerBox.left + "px"; | ||
wrapper.style.pos = options.pos.top - outerBox.top + "px"; | ||
wrapper.style.top = options.pos.top - outerBox.top + "px"; | ||
} else { | ||
@@ -299,2 +307,2 @@ var blockBox = wrapper.getBoundingClientRect(); | ||
(0, _dom.insertCSS)("\n.ProseMirror-prompt {\n background: white;\n padding: 2px 6px 2px 15px;\n border: 1px solid silver;\n position: absolute;\n border-radius: 3px;\n z-index: 11;\n}\n\n.ProseMirror-prompt input[type=\"text\"],\n.ProseMirror-prompt textarea {\n background: #eee;\n border: none;\n outline: none;\n}\n\n.ProseMirror-prompt input[type=\"text\"] {\n padding: 0 4px;\n}\n\n.ProseMirror-prompt-close {\n position: absolute;\n left: 2px; top: 1px;\n color: #666;\n border: none; background: transparent; padding: 0;\n}\n\n.ProseMirror-prompt-close:after {\n content: \"✕\";\n font-size: 12px;\n}\n\n.ProseMirror-invalid {\n background: #ffc;\n border: 1px solid #cc7;\n border-radius: 4px;\n padding: 5px 10px;\n position: absolute;\n min-width: 10em;\n}\n"); | ||
(0, _dom.insertCSS)("\n.ProseMirror-prompt {\n background: white;\n padding: 2px 6px 2px 15px;\n border: 1px solid silver;\n position: absolute;\n border-radius: 3px;\n z-index: 11;\n}\n\n.ProseMirror-prompt h5 {\n margin: 0;\n font-weight: normal;\n font-size: 100%;\n color: #444;\n}\n\n.ProseMirror-prompt input[type=\"text\"],\n.ProseMirror-prompt textarea {\n background: #eee;\n border: none;\n outline: none;\n}\n\n.ProseMirror-prompt input[type=\"text\"] {\n padding: 0 4px;\n}\n\n.ProseMirror-prompt-close {\n position: absolute;\n left: 2px; top: 1px;\n color: #666;\n border: none; background: transparent; padding: 0;\n}\n\n.ProseMirror-prompt-close:after {\n content: \"✕\";\n font-size: 12px;\n}\n\n.ProseMirror-invalid {\n background: #ffc;\n border: 1px solid #cc7;\n border-radius: 4px;\n padding: 5px 10px;\n position: absolute;\n min-width: 10em;\n}\n\n.ProseMirror-prompt-buttons {\n margin-top: 5px;\n display: none;\n}\n\n"); |
@@ -23,12 +23,15 @@ "use strict"; | ||
var Tooltip = exports.Tooltip = function () { | ||
// :: (DOMNode, string) | ||
// :: (DOMNode, ?union<string, Object) | ||
// Create a new tooltip that lives in the wrapper node, which should | ||
// be its offset anchor, i.e. it should have a `relative` or | ||
// `absolute` CSS position. You'll often want to pass an editor's | ||
// [`wrapper` node](#ProseMirror.wrapper). `dir` may be `"above"`, | ||
// `"below"`, `"right"`, `"left"`, or `"center"`. In the latter | ||
// case, the tooltip has no arrow and is positioned centered in its | ||
// wrapper node. | ||
// [`wrapper` node](#ProseMirror.wrapper). `options` may be an object | ||
// containg a `direction` string and a `getBoundingRect` function which | ||
// should return a rectangle determining the space in which the tooltip | ||
// may appear. Alternatively, `options` may be a string specifying the | ||
// direction. The direction can be `"above"`, `"below"`, `"right"`, | ||
// `"left"`, or `"center"`. In the latter case, the tooltip has no arrow | ||
// and is positioned centered in its wrapper node. | ||
function Tooltip(wrapper, dir) { | ||
function Tooltip(wrapper, options) { | ||
var _this = this; | ||
@@ -39,3 +42,4 @@ | ||
this.wrapper = wrapper; | ||
this.dir = dir || "above"; | ||
this.options = typeof options == "string" ? { direction: options } : options; | ||
this.dir = this.options.direction || "above"; | ||
this.pointer = wrapper.appendChild((0, _dom.elt)("div", { class: prefix + "-pointer-" + this.dir + " " + prefix + "-pointer" })); | ||
@@ -91,2 +95,6 @@ this.pointerWidth = this.pointerHeight = null; | ||
// Use the window as the bounding rectangle if no getBoundingRect | ||
// function is defined | ||
var boundingRect = (this.options.getBoundingRect || windowRect)(); | ||
for (var child = this.dom.firstChild, next; child; child = next) { | ||
@@ -110,3 +118,3 @@ next = child.nextSibling; | ||
if (this.dir == "above" || this.dir == "below") { | ||
var tipLeft = Math.max(0, Math.min(left - size.width / 2, window.innerWidth - size.width)); | ||
var tipLeft = Math.max(boundingRect.left, Math.min(left - size.width / 2, boundingRect.right - size.width)); | ||
this.dom.style.left = tipLeft - around.left + "px"; | ||
@@ -138,4 +146,4 @@ this.pointer.style.left = left - around.left - this.pointerWidth / 2 + "px"; | ||
} else if (this.dir == "center") { | ||
var _top = Math.max(around.top, 0), | ||
bottom = Math.min(around.bottom, window.innerHeight); | ||
var _top = Math.max(around.top, boundingRect.top), | ||
bottom = Math.min(around.bottom, boundingRect.bottom); | ||
var fromTop = (bottom - _top - size.height) / 2; | ||
@@ -168,2 +176,9 @@ this.dom.style.left = (around.width - size.width) / 2 + "px"; | ||
function windowRect() { | ||
return { | ||
left: 0, right: window.innerWidth, | ||
top: 0, bottom: window.innerHeight | ||
}; | ||
} | ||
(0, _dom.insertCSS)("\n\n." + prefix + " {\n position: absolute;\n display: none;\n box-sizing: border-box;\n -moz-box-sizing: border- box;\n overflow: hidden;\n\n -webkit-transition: width 0.4s ease-out, height 0.4s ease-out, left 0.4s ease-out, top 0.4s ease-out, opacity 0.2s;\n -moz-transition: width 0.4s ease-out, height 0.4s ease-out, left 0.4s ease-out, top 0.4s ease-out, opacity 0.2s;\n transition: width 0.4s ease-out, height 0.4s ease-out, left 0.4s ease-out, top 0.4s ease-out, opacity 0.2s;\n opacity: 0;\n\n border-radius: 5px;\n padding: 3px 7px;\n margin: 0;\n background: white;\n border: 1px solid #777;\n color: #555;\n\n z-index: 11;\n}\n\n." + prefix + "-pointer {\n position: absolute;\n display: none;\n width: 0; height: 0;\n\n -webkit-transition: left 0.4s ease-out, top 0.4s ease-out, opacity 0.2s;\n -moz-transition: left 0.4s ease-out, top 0.4s ease-out, opacity 0.2s;\n transition: left 0.4s ease-out, top 0.4s ease-out, opacity 0.2s;\n opacity: 0;\n\n z-index: 12;\n}\n\n." + prefix + "-pointer:after {\n content: \"\";\n position: absolute;\n display: block;\n}\n\n." + prefix + "-pointer-above {\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 6px solid #777;\n}\n\n." + prefix + "-pointer-above:after {\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 6px solid white;\n left: -6px; top: -7px;\n}\n\n." + prefix + "-pointer-below {\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-bottom: 6px solid #777;\n}\n\n." + prefix + "-pointer-below:after {\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-bottom: 6px solid white;\n left: -6px; top: 1px;\n}\n\n." + prefix + "-pointer-right {\n border-top: 6px solid transparent;\n border-bottom: 6px solid transparent;\n border-right: 6px solid #777;\n}\n\n." + prefix + "-pointer-right:after {\n border-top: 6px solid transparent;\n border-bottom: 6px solid transparent;\n border-right: 6px solid white;\n left: 1px; top: -6px;\n}\n\n." + prefix + "-pointer-left {\n border-top: 6px solid transparent;\n border-bottom: 6px solid transparent;\n border-left: 6px solid #777;\n}\n\n." + prefix + "-pointer-left:after {\n border-top: 6px solid transparent;\n border-bottom: 6px solid transparent;\n border-left: 6px solid white;\n left: -7px; top: -6px;\n}\n\n." + prefix + " input[type=\"text\"],\n." + prefix + " textarea {\n background: #eee;\n border: none;\n outline: none;\n}\n\n." + prefix + " input[type=\"text\"] {\n padding: 0 4px;\n}\n\n"); |
@@ -11,2 +11,10 @@ "use strict"; | ||
var noHandlers = []; | ||
function getHandlers(obj, type, copy) { | ||
var arr = obj._handlers && obj._handlers[type]; | ||
if (!arr) return noHandlers; | ||
return !copy || arr.length < 2 ? arr : arr.slice(); | ||
} | ||
var methods = { | ||
@@ -26,4 +34,4 @@ // :: (type: string, handler: (...args: [any])) #path=EventMixin.on | ||
off: function off(type, handler) { | ||
var arr = this._handlers && this._handlers[type]; | ||
if (arr) for (var i = 0; i < arr.length; ++i) { | ||
var arr = getHandlers(this, type, false); | ||
for (var i = 0; i < arr.length; ++i) { | ||
if (arr[i] == handler) { | ||
@@ -41,3 +49,3 @@ arr.splice(i, 1);break; | ||
signal: function signal(type) { | ||
var arr = this._handlers && this._handlers[type]; | ||
var arr = getHandlers(this, type, true); | ||
@@ -48,3 +56,3 @@ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
if (arr) for (var i = 0; i < arr.length; ++i) { | ||
for (var i = 0; i < arr.length; ++i) { | ||
arr[i].apply(arr, args); | ||
@@ -55,20 +63,20 @@ } | ||
// :: (type: string, ...args: [any]) → any #path=EventMixin.signalHandleable | ||
// Signal a handleable event of the given type. All handlers for the | ||
// event will be called with the given arguments, until one of them | ||
// returns something that is not the value `false`. When that | ||
// happens, the return value of that handler is returned. If that | ||
// does not happen, `false` is returned. | ||
// :: (type: string, ...args: [any]) → any | ||
// #path=EventMixin.signalHandleable Signal a handleable event of | ||
// the given type. All handlers for the event will be called with | ||
// the given arguments, until one of them returns something that is | ||
// not the value `null` or `undefined`. When that happens, the | ||
// return value of that handler is returned. If that does not | ||
// happen, `undefined` is returned. | ||
signalHandleable: function signalHandleable(type) { | ||
var arr = this._handlers && this._handlers[type]; | ||
if (arr) { | ||
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
args[_key2 - 1] = arguments[_key2]; | ||
} | ||
var arr = getHandlers(this, type, true); | ||
for (var i = 0; i < arr.length; ++i) { | ||
var result = arr[i].apply(arr, args); | ||
if (result !== false) return result; | ||
} | ||
}return false; | ||
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
args[_key2 - 1] = arguments[_key2]; | ||
} | ||
for (var i = 0; i < arr.length; ++i) { | ||
var result = arr[i].apply(arr, args); | ||
if (result != null) return result; | ||
} | ||
}, | ||
@@ -83,4 +91,4 @@ | ||
signalPipelined: function signalPipelined(type, value) { | ||
var arr = this._handlers && this._handlers[type]; | ||
if (arr) for (var i = 0; i < arr.length; ++i) { | ||
var arr = getHandlers(this, type, true); | ||
for (var i = 0; i < arr.length; ++i) { | ||
value = arr[i](value); | ||
@@ -98,7 +106,6 @@ }return value; | ||
signalDOM: function signalDOM(event, type) { | ||
var arr = this._handlers && this._handlers[type || event.type]; | ||
if (arr) for (var i = 0; i < arr.length; ++i) { | ||
var arr = getHandlers(this, type || event.type, true); | ||
for (var i = 0; i < arr.length; ++i) { | ||
if (arr[i](event) || event.defaultPrevented) return true; | ||
} | ||
return false; | ||
}return false; | ||
}, | ||
@@ -110,4 +117,3 @@ | ||
hasHandler: function hasHandler(type) { | ||
var arr = this._handlers && this._handlers[type]; | ||
return arr && arr.length > 0; | ||
return getHandlers(this, type, false).length > 0; | ||
} | ||
@@ -114,0 +120,0 @@ }; |
{ | ||
"name": "prosemirror", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Well-defined WYSIWYG editor", | ||
@@ -28,3 +28,3 @@ "main": "dist/edit/index.js", | ||
"jsdom": "^8.0.4", | ||
"moduleserve": "^0.6.0", | ||
"moduleserve": "^0.7.0", | ||
"distfs": "^0.1.1", | ||
@@ -31,0 +31,0 @@ "punycode": "^1.4.0", |
# ProseMirror | ||
[![Join the chat at https://gitter.im/ProseMirror/prosemirror](https://badges.gitter.im/ProseMirror/prosemirror.svg)](https://gitter.im/ProseMirror/prosemirror?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
This is a well-behaved rich semantic content editor based on | ||
@@ -4,0 +6,0 @@ contentEditable, with support for collaborative editing and |
import {defineOption} from "../edit" | ||
import {eventMixin} from "../util/event" | ||
import {AssertionError} from "../util/error" | ||
import {Transform} from "../transform" | ||
@@ -117,18 +118,19 @@ import {rebaseSteps} from "./rebase" | ||
receive(steps) { | ||
let doc = this.versionDoc | ||
let maps = steps.map(step => { | ||
let result = step.apply(doc) | ||
doc = result.doc | ||
return result.map | ||
}) | ||
let transform = new Transform(this.versionDoc) | ||
steps.forEach(step => transform.step(step)) | ||
this.version += steps.length | ||
this.versionDoc = doc | ||
this.versionDoc = transform.doc | ||
let rebased = rebaseSteps(doc, maps, this.unconfirmedSteps, this.unconfirmedMaps) | ||
let rebased = rebaseSteps(transform.doc, transform.maps, this.unconfirmedSteps, this.unconfirmedMaps) | ||
this.unconfirmedSteps = rebased.transform.steps.slice() | ||
this.unconfirmedMaps = rebased.transform.maps.slice() | ||
let selectionBefore = this.pm.selection | ||
this.pm.updateDoc(rebased.doc, rebased.mapping) | ||
this.pm.history.rebased(maps, rebased.transform, rebased.positions) | ||
return maps | ||
this.pm.history.rebased(transform.maps, rebased.transform, rebased.positions) | ||
// :: (transform: Transform, selectionBeforeTransform: Selection) #path=Collab#events#collabTransform | ||
// Signals that a transformation has been aplied to the editor. Passes the `Transform` and the selection | ||
// before the transform as arguments to the handler. | ||
this.signal("collabTransform", transform, selectionBefore) | ||
return transform.maps | ||
} | ||
@@ -135,0 +137,0 @@ } |
@@ -66,4 +66,9 @@ import {Pos} from "../model" | ||
let before = around.child(cut.offset - 1), after = around.child(cut.offset) | ||
if (before.type.canContainContent(after.type) && pm.tr.join(cut).apply(pm.apply.scroll) !== false) | ||
return | ||
if (before.type.canContainContent(after.type)) { | ||
let tr = pm.tr.join(cut) | ||
if (tr.steps.length && before.size == 0 && !before.sameMarkup(after)) | ||
tr.setNodeType(cut.move(-1), after.type, after.attrs) | ||
if (tr.apply(pm.apply.scroll) !== false) | ||
return | ||
} | ||
@@ -109,2 +114,10 @@ let conn | ||
// If the node below has no content and the node above is | ||
// selectable, delete the node below and select the one above. | ||
if (before.type.contains == null && before.type.selectable && pm.doc.path(head.path).size == 0) { | ||
let tr = pm.tr.delete(cut, cut.move(1)).apply(pm.apply.scroll) | ||
pm.setNodeSelection(cut.move(-1)) | ||
return tr | ||
} | ||
// If the node doesn't allow children, delete it | ||
@@ -321,3 +334,3 @@ if (before.type.contains == null) | ||
// | ||
// **Keybindings:** Alt-Left | ||
// **Keybindings:** Ctrl-[ | ||
baseCommands.lift = { | ||
@@ -341,3 +354,3 @@ label: "Lift out of enclosing block", | ||
}, | ||
keys: ["Alt-Left"] | ||
keys: ["Mod-["] | ||
} | ||
@@ -501,3 +514,6 @@ | ||
}, | ||
keys: ["Left", "Mod-Left"] | ||
keys: { | ||
all: ["Left", "Mod-Left"], | ||
mac: ["Alt-Left"] | ||
} | ||
} | ||
@@ -516,5 +532,12 @@ | ||
}, | ||
keys: ["Right", "Mod-Right"] | ||
keys: { | ||
all: ["Right", "Mod-Right"], | ||
mac: ["Alt-Left"] | ||
} | ||
} | ||
// : (ProseMirror, number) | ||
// Check whether vertical selection motion would involve node | ||
// selections. If so, apply it (if not, the result is left to the | ||
// browser) | ||
function selectNodeVertically(pm, dir) { | ||
@@ -521,0 +544,0 @@ let {empty, node, from, to} = pm.selection |
@@ -5,2 +5,3 @@ import {Pos, findDiffStart, findDiffEnd} from "../model" | ||
import {findSelectionFrom} from "./selection" | ||
import {findByPath} from "./dompos" | ||
@@ -51,3 +52,3 @@ | ||
export function applyDOMChange(pm) { | ||
export function readDOMChange(pm) { | ||
let updated = parseNearSelection(pm) | ||
@@ -60,4 +61,13 @@ let changeStart = findDiffStart(pm.doc.content, updated.content) | ||
pm.tr.replace(changeStart, changeEnd.a, updated, changeStart, changeEnd.b).apply() | ||
return true | ||
let near | ||
// FIXME when we have a Slice type, just return replace info, & let caller inspect it | ||
if (pm.doc.path(changeStart.path).isTextblock && | ||
Pos.samePath(changeStart.path, changeEnd.a.path) && | ||
!Pos.samePath(changeStart.path, changeEnd.b.path) && | ||
(near = findSelectionFrom(updated, after(updated, changeStart), 1, true)) && | ||
!near.head.cmp(changeEnd.b)) | ||
return {type: "enter"} | ||
else | ||
return {type: "replace", | ||
run: () => pm.tr.replace(changeStart, changeEnd.a, updated, changeStart, changeEnd.b).apply()} | ||
} else { | ||
@@ -68,2 +78,9 @@ return false | ||
function after(doc, pos) { | ||
if (pos.offset < doc.path(pos.path).size) | ||
return pos.move(1) | ||
else | ||
return pos.shorten(null, 1) | ||
} | ||
function offsetBy(first, second, pos) { | ||
@@ -70,0 +87,0 @@ let same = samePathDepth(first, second) |
@@ -332,2 +332,7 @@ import {Pos} from "../model" | ||
// :: (pm: ProseMirror, event: MouseEvent, path: [number], node: Node) → bool | ||
// #path=NodeType.prototype.handleDoubleClick | ||
// This works like [`handleClick`](#NodeType.handleClick), but is | ||
// called for double clicks instead. | ||
// :: (pm: ProseMirror, event: MouseEvent, path: [number], node: Node) → bool | ||
// #path=NodeType.prototype.handleContextMenu | ||
@@ -334,0 +339,0 @@ // |
@@ -13,10 +13,12 @@ import {Pos} from "../model" | ||
onRender(node, dom, offset) { | ||
if (!node.isText && node.type.contains == null) { | ||
dom.contentEditable = false | ||
if (node.isBlock) dom.setAttribute("pm-leaf", "true") | ||
if (node.isBlock) { | ||
if (node.type.contains == null) | ||
dom.setAttribute("pm-leaf", "true") | ||
if (offset != null) | ||
dom.setAttribute("pm-offset", offset) | ||
if (node.isTextblock) | ||
adjustTrailingHacks(dom, node) | ||
if (dom.contentEditable == "false") | ||
dom = elt("div", dom) | ||
} | ||
if (node.isBlock && offset != null) | ||
dom.setAttribute("pm-offset", offset) | ||
if (node.isTextblock) | ||
adjustTrailingHacks(dom, node) | ||
@@ -23,0 +25,0 @@ return dom |
@@ -14,2 +14,11 @@ import {Pos} from "../model" | ||
class HistoryEvent { | ||
constructor(steps, selection) { | ||
this.steps = steps | ||
this.selection = selection | ||
} | ||
} | ||
// Assists with remapping a step with other changes that have been | ||
// made since the step was first applied. | ||
class BranchRemapping { | ||
@@ -19,2 +28,4 @@ constructor(branch) { | ||
this.remap = new Remapping | ||
// Track the internal version of what step the current remapping collection | ||
// would put the content at. | ||
this.version = branch.version | ||
@@ -24,2 +35,4 @@ this.mirrorBuffer = Object.create(null) | ||
// Add all position maps between the current version | ||
// and the desired version to the remapping collection. | ||
moveToVersion(version) { | ||
@@ -29,2 +42,4 @@ while (this.version > version) this.addNextMap() | ||
// Add the next map at the current version to the | ||
// remapping collection. | ||
addNextMap() { | ||
@@ -45,4 +60,10 @@ let found = this.branch.mirror[this.version] | ||
const workTime = 100, pauseTime = 150 | ||
// The number of milliseconds the compression worker has to compress. | ||
const workTime = 100 | ||
// The number of milliseconds to pause compression worker if it uses | ||
// all its work time. | ||
const pauseTime = 150 | ||
// Help compress steps in events for a branch. | ||
class CompressionWorker { | ||
@@ -64,2 +85,3 @@ constructor(doc, branch, callback) { | ||
// Compress steps in all events in the branch. | ||
work() { | ||
@@ -72,12 +94,16 @@ if (this.aborted) return | ||
if (this.i == 0) return this.finish() | ||
let event = this.branch.events[--this.i], outEvent = [] | ||
for (let j = event.length - 1; j >= 0; j--) { | ||
let {step, version: stepVersion, id: stepID} = event[j] | ||
let event = this.branch.events[--this.i] | ||
let mappedSelection = event.selection && event.selection.map(this.doc, this.remap.remap) | ||
let outEvent = new HistoryEvent([], mappedSelection) | ||
for (let j = event.steps.length - 1; j >= 0; j--) { | ||
let {step, version: stepVersion, id: stepID} = event.steps[j] | ||
this.remap.moveToVersion(stepVersion) | ||
let mappedStep = step.map(this.remap.remap) | ||
// Combine contiguous delete steps. | ||
if (mappedStep && isDelStep(step)) { | ||
let extra = 0, start = step.from | ||
while (j > 0) { | ||
let next = event[j - 1] | ||
let next = event.steps[j - 1] | ||
if (next.version != stepVersion - 1 || !isDelStep(next.step) || | ||
@@ -101,3 +127,3 @@ start.cmp(next.step.to)) | ||
this.maps.push(result.map.invert()) | ||
outEvent.push(new InvertedStep(mappedStep, this.version, stepID)) | ||
outEvent.steps.push(new InvertedStep(mappedStep, this.version, stepID)) | ||
this.version-- | ||
@@ -107,4 +133,4 @@ } | ||
} | ||
if (outEvent.length) { | ||
outEvent.reverse() | ||
if (outEvent.steps.length) { | ||
outEvent.steps.reverse() | ||
this.events.push(outEvent) | ||
@@ -139,2 +165,3 @@ } | ||
// The minimum number of new steps before a compression is started. | ||
const compressStepCount = 150 | ||
@@ -167,5 +194,8 @@ | ||
newEvent() { | ||
// : (Selection) | ||
// Create a new history event at tip of the branch. | ||
newEvent(currentSelection) { | ||
this.abortCompression() | ||
this.events.push([]) | ||
this.events.push(new HistoryEvent([], currentSelection)) | ||
while (this.events.length > this.maxDepth) | ||
@@ -175,2 +205,6 @@ this.events.shift() | ||
// : (PosMap) | ||
// Add a position map to the branch, either representing one of the | ||
// changes recorded in the branch, or representing a non-history | ||
// change that the branch's changes must be mapped through. | ||
addMap(map) { | ||
@@ -185,2 +219,4 @@ if (!this.empty()) { | ||
// : () → bool | ||
// Whether the branch is empty (has no history events). | ||
empty() { | ||
@@ -193,3 +229,3 @@ return this.events.length == 0 | ||
if (id == null) id = this.nextStepID++ | ||
this.events[this.events.length - 1].push(new InvertedStep(step, this.version, id)) | ||
this.events[this.events.length - 1].steps.push(new InvertedStep(step, this.version, id)) | ||
} | ||
@@ -207,3 +243,3 @@ | ||
// : (Node, bool) → ?{transform: Transform, ids: [number]} | ||
// : (Node, bool) → ?{transform: Transform, ids: [number], selection: Selection} | ||
// Pop the latest event off the branch's history and apply it | ||
@@ -220,9 +256,11 @@ // to a document transform, returning the transform and the step ID. | ||
for (let i = event.length - 1; i >= 0; i--) { | ||
let invertedStep = event[i], step = invertedStep.step | ||
for (let i = event.steps.length - 1; i >= 0; i--) { | ||
let invertedStep = event.steps[i], step = invertedStep.step | ||
if (!collapsing || invertedStep.version != remap.version) { | ||
collapsing = false | ||
// Remap the step through any position mappings unrelated to | ||
// history (e.g. collaborative edits). | ||
remap.moveToVersion(invertedStep.version) | ||
step = step.map(remap.remap) | ||
step = step.map(remap.remap) | ||
let result = step && tr.step(step) | ||
@@ -235,3 +273,3 @@ if (result) { | ||
if (i > 0) remap.movePastStep(result) | ||
remap.movePastStep(result) | ||
} else { | ||
@@ -246,4 +284,6 @@ this.version-- | ||
} | ||
let selection = event.selection && event.selection.map(tr.doc, remap.remap) | ||
if (this.empty()) this.clear(true) | ||
return {transform: tr, ids} | ||
return {transform: tr, ids, selection} | ||
} | ||
@@ -254,3 +294,3 @@ | ||
let event = this.events[i] | ||
if (event.length) return event[event.length - 1] | ||
if (event.steps.length) return event.steps[event.steps.length - 1] | ||
} | ||
@@ -270,6 +310,10 @@ } | ||
findVersion(version) { | ||
// FIXME this is not accurate when the actual revision has fallen | ||
// off the end of the history. Current representation of versions | ||
// does not allow us to recognize that case. | ||
if (version.lastID == null) return {event: 0, step: 0} | ||
for (let i = this.events.length - 1; i >= 0; i--) { | ||
let event = this.events[i] | ||
for (let j = event.length - 1; j >= 0; j--) | ||
if (event[j].id <= version.lastID) | ||
for (let j = event.steps.length - 1; j >= 0; j--) | ||
if (event.steps[j].id <= version.lastID) | ||
return {event: i, step: j + 1} | ||
@@ -288,12 +332,12 @@ } | ||
let event = this.events[i] | ||
for (let j = event.length - 1; j >= 0; j--) { | ||
let step = event[j] | ||
for (let j = event.steps.length - 1; j >= 0; j--) { | ||
let step = event.steps[j] | ||
if (step.version <= startVersion) break out | ||
let off = positions[step.version - startVersion - 1] | ||
if (off == -1) { | ||
event.splice(j--, 1) | ||
event.steps.splice(j--, 1) | ||
} else { | ||
let inv = rebasedTransform.steps[off].invert(rebasedTransform.docs[off], | ||
rebasedTransform.maps[off]) | ||
event[j] = new InvertedStep(inv, startVersion + newMaps.length + off + 1, step.id) | ||
event.steps[j] = new InvertedStep(inv, startVersion + newMaps.length + off + 1, step.id) | ||
} | ||
@@ -337,2 +381,3 @@ } | ||
// Delay between transforms required to compress steps. | ||
const compressDelay = 750 | ||
@@ -353,3 +398,3 @@ | ||
pm.on("transform", (transform, options) => this.recordTransform(transform, options)) | ||
pm.on("transform", (transform, selection, options) => this.recordTransform(transform, selection, options)) | ||
} | ||
@@ -359,3 +404,3 @@ | ||
// Record a transformation in undo history. | ||
recordTransform(transform, options) { | ||
recordTransform(transform, selection, options) { | ||
if (this.ignoreTransform) return | ||
@@ -374,3 +419,3 @@ | ||
if (now > this.lastAddedAt + this.pm.options.historyEventDelay) | ||
this.done.newEvent() | ||
this.done.newEvent(selection) | ||
@@ -413,6 +458,7 @@ this.done.addTransform(transform) | ||
if (!event) return false | ||
let {transform, ids} = event | ||
let {transform, ids, selection} = event | ||
let selectionBeforeTransform = this.pm.selection | ||
this.ignoreTransform = true | ||
this.pm.apply(transform) | ||
this.pm.apply(transform, {selection}) | ||
this.ignoreTransform = false | ||
@@ -423,3 +469,7 @@ | ||
if (to) { | ||
to.newEvent() | ||
// Store the selection before transform on the event so that | ||
// it can be reapplied if the event is undone or redone (e.g. | ||
// redoing a character addition should place the cursor after | ||
// the character). | ||
to.newEvent(selectionBeforeTransform) | ||
to.addTransform(transform, ids) | ||
@@ -454,7 +504,9 @@ } | ||
let event = this.done.events[found.event] | ||
if (found.event == this.done.events.length - 1 && found.step == event.length) return true | ||
let combined = this.done.events.slice(found.event + 1) | ||
.reduce((comb, arr) => comb.concat(arr), event.slice(found.step)) | ||
this.done.events.length = found.event + ((event.length = found.step) ? 1 : 0) | ||
this.done.events.push(combined) | ||
if (found.event == this.done.events.length - 1 && found.step == event.steps.length) return true | ||
// Combine all steps past the verion to rollback to into | ||
// one event, and then "undo" that event. | ||
let combinedSteps = this.done.events.slice(found.event + 1) | ||
.reduce((comb, arr) => comb.concat(arr.steps), event.steps.slice(found.step)) | ||
this.done.events.length = found.event + ((event.steps.length = found.step) ? 1 : 0) | ||
this.done.events.push(new HistoryEvent(combinedSteps, null)) | ||
@@ -476,2 +528,4 @@ this.shift(this.done) | ||
// : (Branch) | ||
// Schedule compression for a branch if it needs compressing. | ||
maybeScheduleCompressionForBranch(branch) { | ||
@@ -478,0 +532,0 @@ window.clearTimeout(branch.compressTimeout) |
@@ -6,6 +6,6 @@ import Keymap from "browserkeymap" | ||
import {captureKeys} from "./capturekeys" | ||
import {elt, browser} from "../dom" | ||
import {elt, browser, contains} from "../dom" | ||
import {applyDOMChange, textContext, textInContext} from "./domchange" | ||
import {TextSelection, rangeFromDOMLoose, findSelectionAtStart, findSelectionAtEnd} from "./selection" | ||
import {readDOMChange, textContext, textInContext} from "./domchange" | ||
import {TextSelection, rangeFromDOMLoose, findSelectionAtStart, findSelectionAtEnd, hasFocus} from "./selection" | ||
import {coordsAtPos, pathFromDOM, handleNodeClick, selectableNodeAbove} from "./dompos" | ||
@@ -127,2 +127,3 @@ | ||
// menus. | ||
if (!hasFocus(pm)) return | ||
pm.signal("interaction") | ||
@@ -152,3 +153,4 @@ if (e.keyCode == 16) pm.input.shiftKey = true | ||
handlers.keypress = (pm, e) => { | ||
if (pm.input.composing || !e.charCode || e.ctrlKey && !e.altKey || browser.mac && e.metaKey) return | ||
if (!hasFocus(pm) || pm.input.composing || !e.charCode || | ||
e.ctrlKey && !e.altKey || browser.mac && e.metaKey) return | ||
if (dispatchKey(pm, Keymap.keyName(e), e)) return | ||
@@ -204,2 +206,3 @@ let sel = pm.selection | ||
if (tripleClick) handleTripleClick(pm, e) | ||
else if (doubleClick && handleNodeClick(pm, "handleDoubleClick", e, true)) {} | ||
else pm.input.mouseDown = new MouseDown(pm, e, doubleClick) | ||
@@ -239,11 +242,11 @@ } | ||
up() { | ||
up(event) { | ||
this.done() | ||
if (this.leaveToBrowser) { | ||
if (this.leaveToBrowser || !contains(this.pm.content, event.target)) { | ||
this.pm.sel.fastPoll() | ||
} else if (this.event.ctrlKey) { | ||
selectClickedNode(this.pm, this.event) | ||
} else if (!handleNodeClick(this.pm, "handleClick", this.event, true)) { | ||
let pos = selectableNodeAbove(this.pm, this.event.target, {left: this.x, top: this.y}) | ||
selectClickedNode(this.pm, event) | ||
} else if (!handleNodeClick(this.pm, "handleClick", event, true)) { | ||
let pos = selectableNodeAbove(this.pm, event.target, {left: this.x, top: this.y}) | ||
if (pos) { | ||
@@ -293,3 +296,3 @@ this.pm.setNodeSelection(pos) | ||
handlers.compositionstart = (pm, e) => { | ||
if (pm.input.maybeAbortComposition()) return | ||
if (!hasFocus(pm) || pm.input.maybeAbortComposition()) return | ||
@@ -303,2 +306,3 @@ pm.flush() | ||
handlers.compositionupdate = (pm, e) => { | ||
if (!hasFocus(pm)) return | ||
let info = pm.input.composing | ||
@@ -315,2 +319,3 @@ if (info && info.data != e.data) { | ||
handlers.compositionend = (pm, e) => { | ||
if (!hasFocus(pm)) return | ||
let info = pm.input.composing | ||
@@ -334,3 +339,4 @@ if (info) { | ||
handlers.input = (pm) => { | ||
handlers.input = (pm, e) => { | ||
if (!hasFocus(pm)) return | ||
if (pm.input.skipInput) return --pm.input.skipInput | ||
@@ -344,3 +350,9 @@ | ||
pm.startOperation({readSelection: false}) | ||
applyDOMChange(pm) | ||
let change = readDOMChange(pm) | ||
if (change) { | ||
if (change.type == "enter") | ||
dispatchKey(pm, "Enter", e) | ||
else | ||
change.run() | ||
} | ||
pm.scrollIntoView() | ||
@@ -444,2 +456,3 @@ } | ||
handlers.paste = (pm, e) => { | ||
if (!hasFocus(pm)) return | ||
if (!e.clipboardData) return | ||
@@ -446,0 +459,0 @@ let sel = pm.selection |
@@ -217,2 +217,7 @@ import "./css" | ||
// | ||
// **`filter`**: ?bool | ||
// : When set to false, suppresses the ability of the | ||
// [`"filterTransform"` event](#ProseMirror.event_beforeTransform) | ||
// to cancel this transform. | ||
// | ||
// Returns the transform, or `false` if there were no steps in it. | ||
@@ -226,2 +231,11 @@ // | ||
// :: (transform: Transform) #path=ProseMirror#events#filterTransform | ||
// Fired before a transform (applied without `filter: false`) is | ||
// applied. The handler can return a truthy value to cancel the | ||
// transform. | ||
if (options.filter !== false && this.signalHandleable("filterTransform", transform)) | ||
return false | ||
let selectionBeforeTransform = this.selection | ||
// :: (transform: Transform, options: Object) #path=ProseMirror#events#beforeTransform | ||
@@ -234,7 +248,8 @@ // Indicates that the given transform is about to be | ||
this.updateDoc(transform.doc, transform, options.selection) | ||
// :: (transfom: Transform, options: Object) #path=ProseMirror#events#transform | ||
// :: (transform: Transform, selectionBeforeTransform: Selection, options: Object) #path=ProseMirror#events#transform | ||
// Signals that a (non-empty) transformation has been aplied to | ||
// the editor. Passes the `Transform` and the options given to | ||
// [`apply`](#ProseMirror.apply) as arguments to the handler. | ||
this.signal("transform", transform, options) | ||
// the editor. Passes the `Transform`, the selection before the | ||
// transform, and the options given to [`apply`](#ProseMirror.apply) | ||
// as arguments to the handler. | ||
this.signal("transform", transform, selectionBeforeTransform, options) | ||
if (options.scrollIntoView) this.scrollIntoView() | ||
@@ -518,2 +533,10 @@ return transform | ||
} | ||
// :: (string) → string | ||
// Return a translated string, if a translate function has been supplied, | ||
// or the original string. | ||
translate(string) { | ||
let trans = this.options.translate | ||
return trans ? trans(string) : string | ||
} | ||
} | ||
@@ -520,0 +543,0 @@ |
@@ -7,5 +7,11 @@ import {defaultSchema} from "../model" | ||
// An option encapsulates functionality for an editor instance, | ||
// e.g. the amount of history events that the editor should hold | ||
// onto or the document's schema. | ||
class Option { | ||
constructor(defaultValue, update, updateOnInit) { | ||
this.defaultValue = defaultValue | ||
// A function that will be invoked with the option's old and new | ||
// value every time the option is [set](#ProseMirror.setOption). | ||
// This function should bootstrap option functionality. | ||
this.update = update | ||
@@ -77,2 +83,8 @@ this.updateOnInit = updateOnInit !== false | ||
// :: ?(string) → string #path=translate #kind=option | ||
// Optional function to translate strings such as menu labels and prompts. | ||
// When set, should be a function that takes a string as argument and returns | ||
// a string, i.e. :: (string) → string | ||
defineOption("translate", null) // FIXME create a way to explicitly force a menu redraw | ||
export function parseOptions(obj) { | ||
@@ -79,0 +91,0 @@ let result = Object.create(null) |
@@ -180,3 +180,3 @@ import {HardBreak, BulletList, OrderedList, ListItem, BlockQuote, Heading, Paragraph, CodeBlock, HorizontalRule, | ||
// | ||
// **Keybindings:** Alt-Right '*', Alt-Right '-' | ||
// **Keybindings:** Shift-Mod-8 | ||
BulletList.register("command", "wrap", { | ||
@@ -193,3 +193,3 @@ derive: {list: true}, | ||
}, | ||
keys: ["Alt-Right '*'", "Alt-Right '-'"] | ||
keys: ["Shift-Mod-8"] | ||
}) | ||
@@ -200,3 +200,3 @@ | ||
// | ||
// **Keybindings:** Alt-Right '1' | ||
// **Keybindings:** Shift-Mod-8 | ||
OrderedList.register("command", "wrap", { | ||
@@ -213,3 +213,3 @@ derive: {list: true}, | ||
}, | ||
keys: ["Alt-Right '1'"] | ||
keys: ["Shift-Mod-9"] | ||
}) | ||
@@ -220,3 +220,3 @@ | ||
// | ||
// **Keybindings:** Alt-Right '>', Alt-Right '"' | ||
// **Keybindings:** Shift-Mod-. | ||
BlockQuote.register("command", "wrap", { | ||
@@ -233,3 +233,3 @@ derive: true, | ||
}, | ||
keys: ["Alt-Right '>'", "Alt-Right '\"'"] | ||
keys: ["Shift-Mod-."] | ||
}) | ||
@@ -282,3 +282,3 @@ | ||
// | ||
// **Keybindings:** Mod-1 through Mod-6 | ||
// **Keybindings:** Shift-Mod-1 through Shift-Mod-6 | ||
Heading.registerComputed("command", "make" + i, type => { | ||
@@ -288,3 +288,3 @@ if (i <= type.maxLevel) return { | ||
label: "Change to heading " + i, | ||
keys: i < 10 && [`Mod-${i}`], | ||
keys: i <= 6 && [`Shift-Mod-${i}`], | ||
menu: { | ||
@@ -301,7 +301,7 @@ group: "textblockHeading", rank: 30 + i, | ||
// | ||
// **Keybindings:** Mod-0 | ||
// **Keybindings:** Shift-Mod-0 | ||
Paragraph.register("command", "make", { | ||
derive: true, | ||
label: "Change to paragraph", | ||
keys: ["Mod-0"], | ||
keys: ["Shift-Mod-0"], | ||
menu: { | ||
@@ -317,7 +317,7 @@ group: "textblock", rank: 10, | ||
// | ||
// **Keybindings:** Mod-\ | ||
// **Keybindings:** Shift-Mod-\ | ||
CodeBlock.register("command", "make", { | ||
derive: true, | ||
label: "Change to code block", | ||
keys: ["Mod-\\"], | ||
keys: ["Shift-Mod-\\"], | ||
menu: { | ||
@@ -324,0 +324,0 @@ group: "textblock", rank: 20, |
@@ -100,4 +100,11 @@ import {Pos} from "../model" | ||
let newRange = findSelectionNear(doc, head, this.range.head && this.range.head.cmp(head) < 0 ? -1 : 1) | ||
if (newRange instanceof TextSelection && doc.path(anchor.path).isTextblock) | ||
if (newRange instanceof TextSelection && doc.path(anchor.path).isTextblock) { | ||
newRange = new TextSelection(anchor, newRange.head) | ||
} else if (newRange instanceof NodeSelection && (anchor.cmp(newRange.from) < 0 || anchor.cmp(newRange.to) > 0)) { | ||
// If head falls on a node, but anchor falls outside of it, | ||
// create a text selection between them | ||
let inv = anchor.cmp(newRange.to) > 0 | ||
newRange = new TextSelection(findSelectionNear(doc, anchor, inv ? -1 : 1, true).anchor, | ||
findSelectionNear(doc, inv ? newRange.from : newRange.to, inv ? 1 : -1, true).head) | ||
} | ||
this.setAndSignal(newRange) | ||
@@ -187,6 +194,6 @@ | ||
// :: Pos #path=Selection.prototype.from | ||
// The start of the selection. | ||
// The left-bound of the selection. | ||
// :: Pos #path=Selection.prototype.to | ||
// The end of the selection. | ||
// The right-bound of the selection. | ||
@@ -285,2 +292,3 @@ // :: bool #path=Selection.empty | ||
export function hasFocus(pm) { | ||
if (document.activeElement != pm.content) return false | ||
let sel = window.getSelection() | ||
@@ -290,2 +298,4 @@ return sel.rangeCount && contains(pm.content, sel.anchorNode) | ||
// Try to find a selection inside the node at the given path coming | ||
// from a given direction. | ||
function findSelectionIn(doc, path, offset, dir, text) { | ||
@@ -295,2 +305,4 @@ let node = doc.path(path) | ||
// Iterate over child nodes recursively coming from the given | ||
// direction and return the first viable selection. | ||
for (let i = offset + (dir > 0 ? 0 : -1); dir > 0 ? i < node.size : i >= 0; i += dir) { | ||
@@ -309,2 +321,6 @@ let child = node.child(i) | ||
// Create a selection which is moved relative to a position in a | ||
// given direction. When a selection isn't found at the given position, | ||
// walks up the document tree one level and one step in the | ||
// desired direction. | ||
export function findSelectionFrom(doc, pos, dir, text) { | ||
@@ -325,2 +341,3 @@ for (let path = pos.path.slice(), offset = pos.offset;;) { | ||
// Find the selection closes to the start of the given node. | ||
export function findSelectionAtStart(node, path = [], text) { | ||
@@ -330,2 +347,3 @@ return findSelectionIn(node, path.slice(), 0, 1, text) | ||
// Find the selection closes to the end of the given node. | ||
export function findSelectionAtEnd(node, path = [], text) { | ||
@@ -335,2 +353,5 @@ return findSelectionIn(node, path.slice(), node.size, -1, text) | ||
// : (ProseMirror, Pos, number) | ||
// Whether vertical position motion in a given direction | ||
// from a position would leave a text block. | ||
export function verticalMotionLeavesTextblock(pm, pos, dir) { | ||
@@ -337,0 +358,0 @@ let dom = pathToDOM(pm.content, pos.path) |
@@ -192,3 +192,3 @@ import {Text, BlockQuote, OrderedList, BulletList, ListItem, | ||
def(HorizontalRule, (_, s) => s.elt("hr")) | ||
def(HorizontalRule, (_, s) => s.elt("div", null, s.elt("hr"))) | ||
@@ -195,0 +195,0 @@ def(Paragraph, (node, s) => s.renderAs(node, "p")) |
@@ -131,3 +131,3 @@ import {Pos} from "../model" | ||
if (child.isText) textBefore += child.text | ||
else textBefore = "" | ||
else textBefore += "\ufffc" | ||
if (i.atEnd() && child.marks.some(st => st.type.isCode)) isCode = true | ||
@@ -134,0 +134,0 @@ } |
@@ -123,30 +123,51 @@ import {Text, BlockQuote, OrderedList, BulletList, ListItem, | ||
renderInline(parent) { | ||
let stack = [] | ||
let active = [] | ||
let progress = node => { | ||
let marks = node ? node.marks.slice() : [] | ||
if (stack.length && stack[stack.length - 1].type == "code" && | ||
(!marks.length || marks[marks.length - 1].type != "code")) { | ||
this.text("`", false) | ||
stack.pop() | ||
} | ||
for (let j = 0; j < stack.length; j++) { | ||
let cur = stack[j], found = false | ||
for (let k = 0; k < marks.length; k++) { | ||
if (marks[k].eq(stack[j])) { | ||
marks.splice(k, 1) | ||
found = true | ||
break | ||
let marks = node ? node.marks : [] | ||
let code = marks.length && marks[marks.length - 1].type.isCode && marks[marks.length - 1] | ||
let len = marks.length - (code ? 1 : 0) | ||
// Try to reorder 'mixable' marks, such as em and strong, which | ||
// in Markdown may be opened and closed in different order, so | ||
// that order of the marks for the token matches the order in | ||
// active. | ||
outer: for (let i = 0; i < len; i++) { | ||
let mark = marks[i] | ||
if (!mark.type.markdownMixable) break | ||
for (let j = 0; j < active.length; j++) { | ||
let other = active[j] | ||
if (!other.type.markdownMixable) break | ||
if (mark.eq(other)) { | ||
if (i > j) | ||
marks = marks.slice(0, j).concat(mark).concat(marks.slice(j, i)).concat(marks.slice(i + 1), len) | ||
else if (j > i) | ||
marks = marks.slice(0, i).concat(marks.slice(i + 1, j)).concat(mark).concat(marks.slice(j, len)) | ||
continue outer | ||
} | ||
} | ||
if (!found) { | ||
this.text(this.markString(cur, false), false) | ||
stack.splice(j--, 1) | ||
} | ||
} | ||
for (let j = 0; j < marks.length; j++) { | ||
let cur = marks[j] | ||
stack.push(cur) | ||
this.text(this.markString(cur, true), false) | ||
// Find the prefix of the mark set that didn't change | ||
let keep = 0 | ||
while (keep < Math.min(active.length, len) && marks[keep].eq(active[keep])) ++keep | ||
// Close the marks that need to be closed | ||
while (keep < active.length) | ||
this.text(this.markString(active.pop(), false), false) | ||
// Open the marks that need to be opened | ||
while (active.length < len) { | ||
let add = marks[active.length] | ||
active.push(add) | ||
this.text(this.markString(add, true), false) | ||
} | ||
if (node) this.render(node) | ||
// Render the node. Special case code marks, since their content | ||
// may not be escaped. | ||
if (node) { | ||
if (code && node.isText) | ||
this.text(this.markString(code, false) + node.text + this.markString(code, true), false) | ||
else | ||
this.render(node) | ||
} | ||
} | ||
@@ -195,2 +216,4 @@ parent.forEach(progress) | ||
// : (Mark, bool) → string | ||
// Get the markdown string for a given opening or closing mark. | ||
markString(mark, open) { | ||
@@ -265,14 +288,12 @@ let value = open ? mark.type.openMarkdown : mark.type.closeMarkdown | ||
function defMark(mark, open, close) { | ||
mark.prototype.openMarkdown = open | ||
mark.prototype.closeMarkdown = close | ||
} | ||
EmMark.prototype.openMarkdown = EmMark.prototype.closeMarkdown = "*" | ||
EmMark.prototype.markdownMixable = true | ||
defMark(EmMark, "*", "*") | ||
StrongMark.prototype.openMarkdown = StrongMark.prototype.closeMarkdown = "**" | ||
StrongMark.prototype.markdownMixable = true | ||
defMark(StrongMark, "**", "**") | ||
LinkMark.prototype.openMarkdown = "[" | ||
LinkMark.prototype.closeMarkdown = (state, mark) => | ||
"](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")" | ||
defMark(LinkMark, "[", | ||
(state, mark) => "](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")") | ||
defMark(CodeMark, "`", "`") | ||
CodeMark.prototype.openMarkdown = CodeMark.prototype.closeMarkdown = "`" |
@@ -32,4 +32,5 @@ import {elt, insertCSS} from "../dom" | ||
if (!command.label) return null | ||
let label = pm.translate(command.label) | ||
let key = command.name && pm.keyForCommand(command.name) | ||
return key ? command.label + " (" + key + ")" : command.label | ||
return key ? label + " (" + key + ")" : label | ||
} | ||
@@ -52,2 +53,6 @@ | ||
get commandName() { | ||
return typeof this.command_ === "string" ? this.command_.command : this.command_.name | ||
} | ||
// :: (ProseMirror) → DOMNode | ||
@@ -75,3 +80,4 @@ // Renders the command according to its [display | ||
} else if (disp.type == "label") { | ||
dom = elt("div", null, disp.label || cmd.spec.label) | ||
let label = pm.translate(disp.label || cmd.spec.label) | ||
dom = elt("div", null, label) | ||
} else { | ||
@@ -89,2 +95,3 @@ throw new AssertionError("Unsupported command display style: " + disp.type) | ||
}) | ||
dom.setAttribute("data-command", this.commandName) | ||
return dom | ||
@@ -175,2 +182,3 @@ } | ||
let label = (this.options.activeLabel && this.findActiveIn(this, pm)) || this.options.label | ||
label = pm.translate(label) | ||
let dom = elt("div", {class: prefix + "-dropdown " + (this.options.class || ""), | ||
@@ -256,3 +264,3 @@ style: this.options.css, | ||
let label = elt("div", {class: prefix + "-submenu-label"}, this.options.label) | ||
let label = elt("div", {class: prefix + "-submenu-label"}, pm.translate(this.options.label)) | ||
let wrap = elt("div", {class: prefix + "-submenu-wrap"}, label, | ||
@@ -259,0 +267,0 @@ elt("div", {class: prefix + "-submenu"}, items)) |
@@ -62,3 +62,3 @@ import {Pos} from "../model" | ||
this.selectedBlockMenu = this.config.selectedBlockMenu | ||
this.updater = new UpdateScheduler(pm, "change selectionChange blur commandsChanged", () => this.update()) | ||
this.updater = new UpdateScheduler(pm, "change selectionChange blur focus commandsChanged", () => this.update()) | ||
this.onContextMenu = this.onContextMenu.bind(this) | ||
@@ -65,0 +65,0 @@ pm.content.addEventListener("contextmenu", this.onContextMenu) |
@@ -86,3 +86,3 @@ import {SchemaSpec, Schema, Block, Textblock, Inline, Text, Attribute, MarkType, NodeKind} from "./schema" | ||
export class EmMark extends MarkType { | ||
static get rank() { return 51 } | ||
static get rank() { return 31 } | ||
} | ||
@@ -92,3 +92,3 @@ | ||
export class StrongMark extends MarkType { | ||
static get rank() { return 52 } | ||
static get rank() { return 32 } | ||
} | ||
@@ -101,3 +101,3 @@ | ||
export class LinkMark extends MarkType { | ||
static get rank() { return 25 } | ||
static get rank() { return 60 } | ||
get attrs() { | ||
@@ -104,0 +104,0 @@ return { |
@@ -326,3 +326,3 @@ import {ModelError} from "./error" | ||
if (end > this.offset) { | ||
let sliceEnd = node.width | ||
let sliceEnd = node.width, sliceStart = this.offset - offset | ||
if (end > this.endOffset) { | ||
@@ -332,3 +332,3 @@ sliceEnd = this.endOffset - offset | ||
} | ||
node = node.copy(node.text.slice(this.offset - offset, sliceEnd)) | ||
node = sliceEnd > sliceStart ? node.copy(node.text.slice(this.offset - offset, sliceEnd)) : null | ||
this.offset = end | ||
@@ -335,0 +335,0 @@ return node |
import {Fragment, emptyFragment} from "./fragment" | ||
import {Mark} from "./mark" | ||
import {Pos} from "./pos" | ||
import {SchemaError} from "./schema" | ||
@@ -365,2 +366,5 @@ const emptyArray = [], emptyAttrs = Object.create(null) | ||
super(type, attrs, null, marks) | ||
if (!content) throw new SchemaError("Empty text nodes are not allowed") | ||
// :: ?string | ||
@@ -367,0 +371,0 @@ // For text nodes, this contains the node's text content. |
@@ -299,15 +299,38 @@ import {Node, TextNode} from "./node" | ||
export class NodeKind { | ||
// :: (string, [NodeKind]) | ||
// :: (string, ?[NodeKind], ?[NodeKind]) | ||
// Create a new node kind with the given set of superkinds (the new | ||
// kind counts as a member of each of the superkinds). The `name` | ||
// field is only for debugging purposes—kind equivalens is defined | ||
// by identity. | ||
constructor(name, ...supers) { | ||
// kind counts as a member of each of the superkinds) and subkinds | ||
// (which will count as a member of this new kind). The `name` field | ||
// is only for debugging purposes—kind equivalens is defined by | ||
// identity. | ||
constructor(name, supers, subs) { | ||
this.name = name | ||
// FIXME temporary backwards-compatibility kludge | ||
if (supers && supers instanceof NodeKind) { | ||
supers = Array.prototype.slice.call(arguments, 1) | ||
subs = null | ||
} | ||
this.id = ++NodeKind.nextID | ||
this.supers = Object.create(null) | ||
this.id = ++NodeKind.nextID | ||
this.supers[this.id] = true | ||
supers.forEach(sup => { for (let id in sup.supers) this.supers[id] = true }) | ||
this.subs = subs || [] | ||
if (supers) supers.forEach(sup => this.addSuper(sup)) | ||
if (subs) subs.forEach(sub => this.addSub(sub)) | ||
} | ||
addSuper(sup) { | ||
for (let id in sup.supers) { | ||
this.supers[id] = true | ||
sup.subs.push(this) | ||
} | ||
} | ||
addSub(sub) { | ||
if (this.supers[sub.id]) | ||
throw new SchemaError("Circular subkind relation") | ||
sub.supers[this.id] = true | ||
sub.subs.forEach(next => this.addSub(next)) | ||
} | ||
// :: (NodeKind) → bool | ||
@@ -330,3 +353,3 @@ // Test whether `other` is a subkind of this kind (or the same | ||
// `NodeKind.inline`. | ||
NodeKind.text = new NodeKind("text", NodeKind.inline) | ||
NodeKind.text = new NodeKind("text", [NodeKind.inline]) | ||
@@ -580,3 +603,4 @@ // ;; Base type for block nodetypes. | ||
// :: (string, ?[Mark]) → Node | ||
// Create a text node in the schema. This method is bound to the Schema. | ||
// Create a text node in the schema. This method is bound to the | ||
// Schema. Empty text nodes are not allowed. | ||
text(text, marks) { | ||
@@ -583,0 +607,0 @@ return this.nodes.text.create(null, text, Mark.setFrom(marks)) |
@@ -117,3 +117,3 @@ import "../../collab" | ||
type(pm1, "E") | ||
conv(pm1, pm2, "ACDE") | ||
conv(pm1, pm2, "ADCE") | ||
}) | ||
@@ -120,0 +120,0 @@ |
@@ -120,2 +120,5 @@ import {defTest} from "../tests" | ||
test("joinBackward", | ||
doc(hr, p("<a>"), hr), | ||
doc(hr, hr)) | ||
test("joinBackward", | ||
doc(hr, blockquote(p("<a>there"))), | ||
@@ -122,0 +125,0 @@ doc(blockquote(p("there")))) |
@@ -1,6 +0,6 @@ | ||
import {applyDOMChange} from "../../edit/domchange" | ||
import {readDOMChange} from "../../edit/domchange" | ||
import {namespace} from "./def" | ||
import {doc, p, em} from "../build" | ||
import {cmpNode} from "../cmp" | ||
import {cmpNode, cmp} from "../cmp" | ||
import {findTextNode} from "./test-selection" | ||
@@ -10,5 +10,9 @@ | ||
function apply(pm) { | ||
readDOMChange(pm).run() | ||
} | ||
test("add_text", pm => { | ||
findTextNode(pm.content, "hello").nodeValue = "heLllo" | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p("heLllo"))) | ||
@@ -19,3 +23,3 @@ }) | ||
findTextNode(pm.content, "hello").nodeValue = "heo" | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p("heo"))) | ||
@@ -27,3 +31,3 @@ }) | ||
txt.parentNode.appendChild(document.createTextNode("!")) | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p("hello!"))) | ||
@@ -35,3 +39,3 @@ }) | ||
txt.parentNode.appendChild(document.createElement("em")).appendChild(document.createTextNode("!")) | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p("hello", em("!")))) | ||
@@ -43,3 +47,3 @@ }) | ||
txt.parentNode.removeChild(txt) | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p())) | ||
@@ -51,3 +55,3 @@ }) | ||
.appendChild(document.createTextNode("hey")) | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p("hey"), p("hello"))) | ||
@@ -59,3 +63,3 @@ }) | ||
.appendChild(document.createTextNode("hello")) | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p("hello"), p("hello"))) | ||
@@ -66,4 +70,11 @@ }) | ||
findTextNode(pm.content, "hello").nodeValue = "helhello" | ||
applyDOMChange(pm) | ||
apply(pm) | ||
cmpNode(pm.doc, doc(p("helhello"))) | ||
}) | ||
test("detect_enter", pm => { | ||
findTextNode(pm.content, "hello").nodeValue = "hel" | ||
pm.content.appendChild(document.createElement("p")).innerHTML = "lo" | ||
let change = readDOMChange(pm) | ||
cmp(change && change.type, "enter") | ||
}) |
import {namespace} from "./def" | ||
import {doc, p} from "../build" | ||
import {is, cmp, cmpNode, P} from "../cmp" | ||
import {is, cmp, cmpStr, cmpNode, P} from "../cmp" | ||
@@ -9,3 +9,3 @@ const test = namespace("history") | ||
function cut(pm) { pm.history.lastAddedAt = 0 } | ||
function cutHistory(pm) { pm.history.lastAddedAt = 0 } | ||
@@ -31,3 +31,3 @@ test("undo", pm => { | ||
type(pm, "a") | ||
cut(pm) | ||
cutHistory(pm) | ||
type(pm, "b") | ||
@@ -57,3 +57,3 @@ cmpNode(pm.doc, doc(p("ab"))) | ||
type(pm, "hello") | ||
cut(pm) | ||
cutHistory(pm) | ||
type(pm, "!") | ||
@@ -72,3 +72,3 @@ pm.tr.insertText(P(0, 0), "....").apply() | ||
type(pm, "hello") | ||
cut(pm) | ||
cutHistory(pm) | ||
pm.tr.delete(P(0, 0), P(0, 5)).apply() | ||
@@ -85,3 +85,3 @@ cmpNode(pm.doc, doc(p())) | ||
type(pm, "hello") | ||
cut(pm) | ||
cutHistory(pm) | ||
pm.tr.delete(P(0, 0), P(0, 5)).apply() | ||
@@ -97,3 +97,3 @@ cmpNode(pm.doc, doc(p())) | ||
type(pm, "hi") | ||
cut(pm) | ||
cutHistory(pm) | ||
type(pm, "hello") | ||
@@ -109,6 +109,6 @@ pm.tr.delete(P(0, 0), P(0, 7)).apply({addToHistory: false}) | ||
type(pm, " two") | ||
cut(pm) | ||
cutHistory(pm) | ||
type(pm, " three") | ||
pm.tr.insertText(P(0, 0), "zero ").apply() | ||
cut(pm) | ||
cutHistory(pm) | ||
pm.tr.split(P(0, 0)).apply() | ||
@@ -128,7 +128,7 @@ pm.setTextSelection(P(0, 0)) | ||
type(pm, " two") | ||
cut(pm) | ||
cutHistory(pm) | ||
pm.tr.insertText(pm.selection.head, "xxx").apply({addToHistory: false}) | ||
type(pm, " three") | ||
pm.tr.insertText(P(0, 0), "zero ").apply() | ||
cut(pm) | ||
cutHistory(pm) | ||
pm.tr.split(P(0, 0)).apply() | ||
@@ -150,3 +150,3 @@ pm.setTextSelection(P(0, 0)) | ||
pm.setTextSelection(P(0, 1)) | ||
cut(pm) | ||
cutHistory(pm) | ||
type(pm, "one") | ||
@@ -160,4 +160,6 @@ type(pm, "two") | ||
cmpNode(pm.doc, doc(p("XY!"))) | ||
cmpStr(pm.selection.anchor, P(0, 1)) | ||
pm.execCommand("redo") | ||
cmpNode(pm.doc, doc(p("XonetwothreeY!"))) | ||
cmpStr(pm.selection.anchor, P(0, 12)) | ||
}) | ||
@@ -174,3 +176,3 @@ | ||
type(pm, "hello") | ||
cut(pm) | ||
cutHistory(pm) | ||
let version = pm.history.getVersion() | ||
@@ -191,3 +193,3 @@ type(pm, "ok") | ||
type(pm, "ok") | ||
cut(pm) | ||
cutHistory(pm) | ||
type(pm, "more") | ||
@@ -201,1 +203,25 @@ is(pm.history.backToVersion(version), "rollback") | ||
}) | ||
test("setSelectionOnUndo", pm => { | ||
type(pm, "hi") | ||
cutHistory(pm) | ||
pm.setTextSelection(P(0, 0), P(0, 2)) | ||
let selection = pm.selection | ||
pm.tr.replaceWith(selection.from, selection.to, pm.schema.text("hello")).apply() | ||
let selection2 = pm.selection | ||
pm.execCommand("undo") | ||
is(pm.selection.eq(selection), "failed restoring selection after undo") | ||
pm.execCommand("redo") | ||
is(pm.selection.eq(selection2), "failed restoring selection after redo") | ||
}) | ||
test("rebaseSelectionOnUndo", pm => { | ||
type(pm, "hi") | ||
cutHistory(pm) | ||
pm.setTextSelection(P(0, 0), P(0, 2)) | ||
pm.tr.insert(P(0, 0), pm.schema.text("hello")).apply() | ||
pm.tr.insert(P(0, 0), pm.schema.text("---")).apply({addToHistory: false}) | ||
pm.execCommand("undo") | ||
cmpStr(pm.selection.head, P(0, 5)) | ||
}) |
import {defaultSchema as schema, Pos} from "../model" | ||
// : (string) → Function | ||
// Create a function for creating inline nodes with brevity. | ||
// For use with `doc()`. | ||
function buildInline(style) { | ||
@@ -9,2 +12,5 @@ return function() { | ||
// : (string, Object) → Function | ||
// Create a function which creating block nodes with brevity. | ||
// For use with `doc()`. | ||
function build(type, attrs) { | ||
@@ -54,2 +60,5 @@ return function() { | ||
// : (...nodes: Node) → Doc | ||
// Create a document node. Child nodes can be added with | ||
// abbreviated node notation, see `build()`. | ||
export function doc() { | ||
@@ -56,0 +65,0 @@ let content = [] |
@@ -47,6 +47,14 @@ import {doc, blockquote, h1, h2, p, hr, li, ol, ul, em, strong, code, a, br, img, dataImage} from "./build" | ||
t("inline_overlap", | ||
t("inline_overlap_mix", | ||
"This is **strong *emphasized text with `code` in* it**", | ||
doc(p("This is ", strong("strong ", em("emphasized text with ", code("code"), " in"), " it")))) | ||
t("inline_overlap_link", | ||
"**[link](http://foo) is bold**", | ||
doc(p(strong(a("link"), " is bold")))) | ||
t("inline_overlap_code", | ||
"**`code` is bold**", | ||
doc(p(strong(code("code"), " is bold")))) | ||
t("link", | ||
@@ -53,0 +61,0 @@ "My [link](http://foo) goes to foo", |
export const tests = Object.create(null) | ||
// :(string, Function) | ||
// Define a test. A test should include a descriptive name and | ||
// a function which runs the test. If a test fails, it should | ||
// throw a Failure. | ||
export function defTest(name, f) { | ||
@@ -4,0 +8,0 @@ if (name in tests) throw new Error("Duplicate definition of test " + name) |
@@ -19,3 +19,3 @@ import {Pos, MarkType} from "../model" | ||
return new StepResult(copyStructure(doc, step.from, step.to, (node, from, to) => { | ||
if (!node.type.canContainMark(step.param)) return node | ||
if (!node.type.canContainMark(step.param.type)) return node | ||
return copyInline(node, from, to, node => { | ||
@@ -22,0 +22,0 @@ return node.mark(step.param.addToSet(node.marks)) |
@@ -34,5 +34,10 @@ import {AssertionError} from "../util/error" | ||
}) | ||
let promptTitle = elt("h5", {}, (command.spec && command.spec.label) ? pm.translate(command.spec.label) : "") | ||
let submitButton = elt("button", {type: "submit", class: "ProseMirror-prompt-submit"}, "Ok") | ||
let cancelButton = elt("button", {type: "button", class: "ProseMirror-prompt-cancel"}, "Cancel") | ||
cancelButton.addEventListener("click", () => this.close()) | ||
// :: DOMNode | ||
// An HTML form wrapping the fields. | ||
this.form = elt("form", null, this.fields.map(f => elt("div", null, f))) | ||
this.form = elt("form", null, promptTitle, this.fields.map(f => elt("div", null, f)), | ||
elt("div", {class: "ProseMirror-prompt-buttons"}, submitButton, " ", cancelButton)) | ||
} | ||
@@ -183,3 +188,3 @@ | ||
return elt("input", {type: "text", | ||
placeholder: param.label, | ||
placeholder: this.translate(param.label), | ||
value, | ||
@@ -196,3 +201,4 @@ autocomplete: "off"}) | ||
let options = param.options.call ? param.options(this) : param.options | ||
return elt("select", null, options.map(o => elt("option", {value: o.value, selected: o.value == value ? "true" : null}, o.label))) | ||
return elt("select", null, options.map(o => elt("option", {value: o.value, selected: o.value == value ? "true" : null}, | ||
this.translate(o.label)))) | ||
}, | ||
@@ -224,3 +230,3 @@ read(dom) { | ||
wrapper.style.left = (options.pos.left - outerBox.left) + "px" | ||
wrapper.style.pos = (options.pos.top - outerBox.top) + "px" | ||
wrapper.style.top = (options.pos.top - outerBox.top) + "px" | ||
} else { | ||
@@ -256,2 +262,9 @@ let blockBox = wrapper.getBoundingClientRect() | ||
.ProseMirror-prompt h5 { | ||
margin: 0; | ||
font-weight: normal; | ||
font-size: 100%; | ||
color: #444; | ||
} | ||
.ProseMirror-prompt input[type="text"], | ||
@@ -288,2 +301,8 @@ .ProseMirror-prompt textarea { | ||
} | ||
.ProseMirror-prompt-buttons { | ||
margin-top: 5px; | ||
display: none; | ||
} | ||
`) |
@@ -11,13 +11,17 @@ import {elt, insertCSS} from "../dom" | ||
export class Tooltip { | ||
// :: (DOMNode, string) | ||
// :: (DOMNode, ?union<string, Object) | ||
// Create a new tooltip that lives in the wrapper node, which should | ||
// be its offset anchor, i.e. it should have a `relative` or | ||
// `absolute` CSS position. You'll often want to pass an editor's | ||
// [`wrapper` node](#ProseMirror.wrapper). `dir` may be `"above"`, | ||
// `"below"`, `"right"`, `"left"`, or `"center"`. In the latter | ||
// case, the tooltip has no arrow and is positioned centered in its | ||
// wrapper node. | ||
constructor(wrapper, dir) { | ||
// [`wrapper` node](#ProseMirror.wrapper). `options` may be an object | ||
// containg a `direction` string and a `getBoundingRect` function which | ||
// should return a rectangle determining the space in which the tooltip | ||
// may appear. Alternatively, `options` may be a string specifying the | ||
// direction. The direction can be `"above"`, `"below"`, `"right"`, | ||
// `"left"`, or `"center"`. In the latter case, the tooltip has no arrow | ||
// and is positioned centered in its wrapper node. | ||
constructor(wrapper, options) { | ||
this.wrapper = wrapper | ||
this.dir = dir || "above" | ||
this.options = typeof options == "string" ? {direction: options} : options | ||
this.dir = this.options.direction || "above" | ||
this.pointer = wrapper.appendChild(elt("div", {class: prefix + "-pointer-" + this.dir + " " + prefix + "-pointer"})) | ||
@@ -66,2 +70,6 @@ this.pointerWidth = this.pointerHeight = null | ||
// Use the window as the bounding rectangle if no getBoundingRect | ||
// function is defined | ||
let boundingRect = (this.options.getBoundingRect || windowRect)() | ||
for (let child = this.dom.firstChild, next; child; child = next) { | ||
@@ -85,3 +93,3 @@ next = child.nextSibling | ||
if (this.dir == "above" || this.dir == "below") { | ||
let tipLeft = Math.max(0, Math.min(left - size.width / 2, window.innerWidth - size.width)) | ||
let tipLeft = Math.max(boundingRect.left, Math.min(left - size.width / 2, boundingRect.right - size.width)) | ||
this.dom.style.left = (tipLeft - around.left) + "px" | ||
@@ -111,3 +119,3 @@ this.pointer.style.left = (left - around.left - this.pointerWidth / 2) + "px" | ||
} else if (this.dir == "center") { | ||
let top = Math.max(around.top, 0), bottom = Math.min(around.bottom, window.innerHeight) | ||
let top = Math.max(around.top, boundingRect.top), bottom = Math.min(around.bottom, boundingRect.bottom) | ||
let fromTop = (bottom - top - size.height) / 2 | ||
@@ -134,2 +142,9 @@ this.dom.style.left = (around.width - size.width) / 2 + "px" | ||
function windowRect() { | ||
return { | ||
left: 0, right: window.innerWidth, | ||
top: 0, bottom: window.innerHeight | ||
} | ||
} | ||
insertCSS(` | ||
@@ -136,0 +151,0 @@ |
@@ -5,2 +5,10 @@ // ;; #path=EventMixin #kind=interface | ||
const noHandlers = [] | ||
function getHandlers(obj, type, copy) { | ||
let arr = obj._handlers && obj._handlers[type] | ||
if (!arr) return noHandlers | ||
return !copy || arr.length < 2 ? arr : arr.slice() | ||
} | ||
const methods = { | ||
@@ -18,4 +26,4 @@ // :: (type: string, handler: (...args: [any])) #path=EventMixin.on | ||
off(type, handler) { | ||
let arr = this._handlers && this._handlers[type] | ||
if (arr) for (let i = 0; i < arr.length; ++i) | ||
let arr = getHandlers(this, type, false) | ||
for (let i = 0; i < arr.length; ++i) | ||
if (arr[i] == handler) { arr.splice(i, 1); break } | ||
@@ -29,20 +37,19 @@ }, | ||
signal(type, ...args) { | ||
let arr = this._handlers && this._handlers[type] | ||
if (arr) for (let i = 0; i < arr.length; ++i) | ||
arr[i](...args) | ||
let arr = getHandlers(this, type, true) | ||
for (let i = 0; i < arr.length; ++i) arr[i](...args) | ||
}, | ||
// :: (type: string, ...args: [any]) → any #path=EventMixin.signalHandleable | ||
// Signal a handleable event of the given type. All handlers for the | ||
// event will be called with the given arguments, until one of them | ||
// returns something that is not the value `false`. When that | ||
// happens, the return value of that handler is returned. If that | ||
// does not happen, `false` is returned. | ||
// :: (type: string, ...args: [any]) → any | ||
// #path=EventMixin.signalHandleable Signal a handleable event of | ||
// the given type. All handlers for the event will be called with | ||
// the given arguments, until one of them returns something that is | ||
// not the value `null` or `undefined`. When that happens, the | ||
// return value of that handler is returned. If that does not | ||
// happen, `undefined` is returned. | ||
signalHandleable(type, ...args) { | ||
let arr = this._handlers && this._handlers[type] | ||
if (arr) for (let i = 0; i < arr.length; ++i) { | ||
let arr = getHandlers(this, type, true) | ||
for (let i = 0; i < arr.length; ++i) { | ||
let result = arr[i](...args) | ||
if (result !== false) return result | ||
if (result != null) return result | ||
} | ||
return false | ||
}, | ||
@@ -56,5 +63,4 @@ | ||
signalPipelined(type, value) { | ||
let arr = this._handlers && this._handlers[type] | ||
if (arr) for (let i = 0; i < arr.length; ++i) | ||
value = arr[i](value) | ||
let arr = getHandlers(this, type, true) | ||
for (let i = 0; i < arr.length; ++i) value = arr[i](value) | ||
return value | ||
@@ -70,6 +76,5 @@ }, | ||
signalDOM(event, type) { | ||
let arr = this._handlers && this._handlers[type || event.type] | ||
if (arr) for (let i = 0; i < arr.length; ++i) { | ||
let arr = getHandlers(this, type || event.type, true) | ||
for (let i = 0; i < arr.length; ++i) | ||
if (arr[i](event) || event.defaultPrevented) return true | ||
} | ||
return false | ||
@@ -81,4 +86,3 @@ }, | ||
hasHandler(type) { | ||
let arr = this._handlers && this._handlers[type] | ||
return arr && arr.length > 0 | ||
return getHandlers(this, type, false).length > 0 | ||
} | ||
@@ -85,0 +89,0 @@ } |
1184370
195
28191
55