prosemirror-transform
Advanced tools
Comparing version 0.24.0 to 1.0.0
@@ -37,2 +37,6 @@ # How to contribute | ||
If you want to make a change that involves a significant overhaul of | ||
the code or introduces a user-visible new feature, create an | ||
[RFC](https://github.com/ProseMirror/rfcs/) first with your proposal. | ||
- Make sure you have a [GitHub Account](https://github.com/signup/free) | ||
@@ -39,0 +43,0 @@ |
{ | ||
"name": "prosemirror-transform", | ||
"version": "0.24.0", | ||
"version": "1.0.0", | ||
"description": "ProseMirror document transformations", | ||
@@ -19,3 +19,3 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"prosemirror-model": "^0.24.0" | ||
"prosemirror-model": "^1.0.0" | ||
}, | ||
@@ -25,3 +25,3 @@ "devDependencies": { | ||
"ist": "^1.0.0", | ||
"prosemirror-test-builder": "^0.24.0", | ||
"prosemirror-test-builder": "^1.0.0", | ||
"rollup": "^0.49.0", | ||
@@ -28,0 +28,0 @@ "rollup-plugin-buble": "^0.15.0" |
@@ -5,2 +5,3 @@ # prosemirror-transform | ||
This is a [core module](http://prosemirror.net/docs/ref/#transform) of [ProseMirror](http://prosemirror.net). | ||
ProseMirror is a well-behaved rich semantic content editor based on | ||
@@ -25,1 +26,6 @@ contentEditable, with support for collaborative editing and custom | ||
is the place to report issues. | ||
We aim to be an inclusive, welcoming community. To make that explicit, | ||
we have a [code of | ||
conduct](http://contributor-covenant.org/version/1/1/0/) that applies | ||
to communication around the project. |
@@ -1,2 +0,2 @@ | ||
import {Fragment, Slice, Mark} from "prosemirror-model" | ||
import {Fragment, Slice} from "prosemirror-model" | ||
@@ -202,2 +202,12 @@ import {ReplaceStep, ReplaceAroundStep} from "./replace_step" | ||
function nodeLeft(content, depth) { | ||
for (let i = 1; i < depth; i++) content = content.firstChild.content | ||
return content.firstChild | ||
} | ||
function nodeRight(content, depth) { | ||
for (let i = 1; i < depth; i++) content = content.lastChild.content | ||
return content.lastChild | ||
} | ||
// Algorithm for 'placing' the elements of a slice into a gap: | ||
@@ -223,80 +233,65 @@ // | ||
function nodeLeft(content, depth) { | ||
for (let i = 1; i < depth; i++) content = content.firstChild.content | ||
return content.firstChild | ||
} | ||
function nodeRight(content, depth) { | ||
for (let i = 1; i < depth; i++) content = content.lastChild.content | ||
return content.lastChild | ||
} | ||
// : (ResolvedPos, Slice) → [{content: Fragment, openEnd: number, depth: number}] | ||
function placeSlice($from, slice) { | ||
let dFrom = $from.depth, unplaced = null | ||
let placed = [], parents = null | ||
let placed = [] | ||
if (!slice.content.size) return placed | ||
// Loop over the open side of the slice, trying to find a place for | ||
// each open fragment. | ||
for (let dSlice = slice.openStart;; --dSlice) { | ||
// Get the components of the node at this level | ||
let curType, curAttrs, curMarks, curFragment | ||
if (dSlice >= 0) { | ||
if (dSlice > 0) { // Inside slice | ||
;({type: curType, attrs: curAttrs, content: curFragment, marks: curMarks} = nodeLeft(slice.content, dSlice)) | ||
} else if (dSlice == 0) { // Top of slice | ||
curFragment = slice.content | ||
curMarks = Mark.none | ||
} | ||
if (dSlice < slice.openStart) curFragment = curFragment.cutByIndex(1, curFragment.childCount) | ||
} else { // Outside slice, in generated wrappers (see below) | ||
curFragment = Fragment.empty | ||
;({type: curType, attrs: curAttrs, marks: curMarks} = parents[parents.length + dSlice - 1]) | ||
// each open fragment. The first pass tries to find direct fits, the | ||
// second allows wrapping. | ||
let dSlice = slice.openStart, lastPlaced = $from.depth + 1 | ||
for (let dFrom = $from.depth, pass = 1; dFrom >= 0 && dSlice >= 0; dFrom--) { | ||
// If we've reached the end of the first pass, go to the second | ||
if (dFrom == 0 && pass == 1) { | ||
dFrom = lastPlaced | ||
pass = 2 | ||
continue | ||
} | ||
// If the last iteration left unplaced content, include it in the fragment | ||
if (unplaced) curFragment = curFragment.addToStart(unplaced) | ||
let parent = $from.node(dFrom), match = parent.contentMatchAt($from.indexAfter(dFrom)) | ||
let existing = placed[dFrom] | ||
let placedHere = existing ? existing.content : Fragment.empty, openEnd = existing ? existing.openEnd : 0 | ||
// If there's nothing left to place, we're done | ||
if (curFragment.size == 0 && dSlice <= 0) break | ||
for (let d = dSlice; d >= 0; d--) { | ||
let content = sliceRange(slice.content, d, dSlice == slice.openStart ? null : dSlice + 1) | ||
// This will go through the positions in $from, down from dFrom, | ||
// to find a fit | ||
let found = findPlacement(curFragment, $from, dFrom, placed) | ||
if (found && unneccesaryFallthrough($from, dFrom, found.depth, slice, dSlice)) | ||
found = null | ||
if (found) { | ||
// If there was a fit, store it, and consider this content placed | ||
if (found.fragment.size > 0) { | ||
let openEnd = endOfContent(slice, dSlice) ? slice.openEnd - dSlice : 0 | ||
if (placed[found.depth] && curFragment.size == 0) openEnd = placed[found.depth].openEnd | ||
placed[found.depth] = {content: found.fragment, openEnd, depth: found.depth} | ||
} | ||
// If that was the last of the content, we're done | ||
if (dSlice <= 0) break | ||
unplaced = null | ||
dFrom = found.depth - (curType == $from.node(found.depth).type ? 1 : 0) | ||
} else { | ||
if (dSlice == 0) { | ||
// This is the top of the slice, and we haven't found a place to insert it. | ||
let top = $from.node(0) | ||
// Try to find a wrapping that makes its first child fit in the top node. | ||
let wrap = top.contentMatchAt($from.index(0)).findWrapping(curFragment.firstChild.type) | ||
// If no such thing exists, give up. | ||
if (!wrap || wrap.length == 0) break | ||
let last = wrap[wrap.length - 1] | ||
// Check that the fragment actually fits in the wrapping. | ||
if (!last.validContent(curFragment)) break | ||
// Store the result for subsequent iterations. | ||
parents = [{type: top.type, attrs: top.attrs}].concat(wrap) | ||
curType = last | ||
curAttrs = null | ||
} | ||
if (curFragment.size) { | ||
curFragment = curType.contentMatch.fillBefore(curFragment, true).append(curFragment) | ||
unplaced = curType.create(curAttrs, curFragment, curMarks) | ||
if (pass == 1) { | ||
// First pass, search for direct fits (possibly by stripping marks | ||
let fits = match.fillBefore(content) | ||
if (!fits && hasMarks(content)) { | ||
let stripped = matchStrippingMarks(parent.type, match, content) | ||
if (stripped) { content = stripped; fits = Fragment.empty } | ||
} | ||
if (fits) { | ||
content = fits.append(closeStart(content, dSlice - d)) | ||
placedHere = placedHere.append(content) | ||
if (content.size) openEnd = endOfContent(slice, d) ? slice.openEnd - d : 0 | ||
dSlice = d - 1 | ||
lastPlaced = dFrom | ||
if (nodeLeft(slice.content, d).type == parent.type) break | ||
} | ||
} else { | ||
unplaced = null | ||
// Second pass, allows introducing wrapper nodes | ||
if (content.size == 0) continue | ||
let wrap = match.findWrapping(content.firstChild.type) | ||
if (!wrap) continue | ||
let atEnd = endOfContent(slice, d) | ||
if (!wrap.length) { | ||
if (!match.matchFragment(content)) continue | ||
} else if (d && wrap[wrap.length - 1] == nodeLeft(slice.content, d).type) { | ||
// Don't create wrappers that correspond to exiting wrapper nodes | ||
continue | ||
} else if (!atEnd) { | ||
let after = wrap[wrap.length - 1].contentMatch.matchFragment(content) | ||
if (!after) continue | ||
content = content.append(after.fillBefore(Fragment.empty, true)) | ||
} | ||
content = closeStart(content, dSlice - d) | ||
for (let i = wrap.length - 1; i >= 0; i--) content = Fragment.from(wrap[i].create(null, content)) | ||
placedHere = placedHere.append(content) | ||
if (content.size) openEnd = atEnd ? wrap.length + slice.openEnd : 0 | ||
dSlice = d - 1 | ||
} | ||
} | ||
if (placedHere.size) placed[dFrom] = {content: placedHere, openEnd, depth: dFrom} | ||
} | ||
@@ -315,17 +310,6 @@ | ||
function findPlacement(fragment, $from, start, placed) { | ||
let hasMarks = false | ||
function hasMarks(fragment) { | ||
for (let i = 0; i < fragment.childCount; i++) | ||
if (fragment.child(i).marks.length) hasMarks = true | ||
for (let d = start; d >= 0; d--) { | ||
let parent = $from.node(d), startMatch = parent.contentMatchAt($from.indexAfter(d)) | ||
let existing = placed[d] | ||
if (existing) startMatch = startMatch.matchFragment(existing.content) | ||
let match = startMatch.fillBefore(fragment) | ||
if (match) return {depth: d, fragment: (existing ? existing.content.append(match) : match).append(fragment)} | ||
if (hasMarks) { | ||
let stripped = matchStrippingMarks(parent.type, startMatch, fragment) | ||
if (stripped) return {depth: d, fragment: existing ? existing.content.append(stripped) : stripped} | ||
} | ||
} | ||
if (fragment.child(i).marks.length) return true | ||
return false | ||
} | ||
@@ -344,14 +328,25 @@ | ||
function unneccesaryFallthrough($from, dFrom, dFound, slice, dSlice) { | ||
if (dSlice < 1) return false | ||
for (; dFrom > dFound; dFrom--) { | ||
let parent = $from.node(dFrom), here = parent.contentMatchAt($from.indexAfter(dFrom)) | ||
for (let d = dSlice - 1; d >= 0; d--) { | ||
let node = nodeLeft(slice.content, d) | ||
if (here.matchType(node.type) && parent.type.allowsMarks(node.marks)) return true | ||
} | ||
} | ||
return false | ||
// : (Fragment, number, ?number) → Fragment | ||
// Pick the fragment at `startDepth` out of a slice's content, | ||
// dropping the first node at depth `endDepth`, if not null. | ||
function sliceRange(content, startDepth, endDepth) { | ||
for (let i = 0; i < startDepth; i++) content = content.firstChild.content | ||
if (endDepth != null) content = dropFirstAt(content, endDepth - startDepth) | ||
return content | ||
} | ||
function dropFirstAt(fragment, depth) { | ||
if (depth == 1) return fragment.cutByIndex(1, fragment.childCount) | ||
let first = fragment.firstChild | ||
return fragment.replaceChild(0, first.copy(dropFirstAt(first.content, depth - 1))) | ||
} | ||
function closeStart(fragment, depth) { | ||
if (depth == 0) return fragment | ||
let first = fragment.firstChild, content = closeStart(first.content, depth - 1) | ||
if (!content.size) return fragment.cutByIndex(1, fragment.childCount) | ||
let fill = first.type.contentMatch.fillBefore(content) | ||
return fragment.replaceChild(0, first.copy(fill.append(content))) | ||
} | ||
// :: (number, number, Slice) → this | ||
@@ -358,0 +353,0 @@ // Replace a range of the document with a given slice, using `from`, |
@@ -118,3 +118,3 @@ import {Slice, Fragment} from "prosemirror-model" | ||
this.doc.nodesBetween(from, to, (node, pos) => { | ||
if (node.isTextblock && !node.hasMarkup(type, attrs)) { | ||
if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(this.doc, this.mapping.slice(mapFrom).map(pos), type)) { | ||
// Ensure all markup that isn't allowed in the new node type is cleared | ||
@@ -132,9 +132,5 @@ this.clearIncompatible(this.mapping.slice(mapFrom).map(pos, 1), type) | ||
let warnedAboutSetNodeType = false | ||
Transform.prototype.setNodeType = function(pos, type, attrs, marks) { | ||
if (!warnedAboutSetNodeType && typeof console == "object" && console.warn) { | ||
warnedAboutSetNodeType = true | ||
console.warn("setNodeType has been renamed to setNodeMarkup") | ||
} | ||
return this.setNodeMarkup(pos, type, attrs, marks) | ||
function canChangeType(doc, pos, type) { | ||
let $pos = doc.resolve(pos), index = $pos.index() | ||
return $pos.parent.canReplaceWith(index, index + 1, type) | ||
} | ||
@@ -165,3 +161,3 @@ | ||
let innerType = (typesAfter && typesAfter[typesAfter.length - 1]) || $pos.parent | ||
if (base < 0 || | ||
if (base < 0 || $pos.parent.type.spec.isolating || | ||
!$pos.parent.canReplace($pos.index(), $pos.parent.childCount) || | ||
@@ -172,2 +168,3 @@ !innerType.type.validContent($pos.parent.content.cutByIndex($pos.index(), $pos.parent.childCount))) | ||
let node = $pos.node(d), index = $pos.index(d) | ||
if (node.type.spec.isolating) return false | ||
let rest = node.content.cutByIndex(index, node.childCount) | ||
@@ -174,0 +171,0 @@ let after = (typesAfter && typesAfter[i]) || node |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
30
256173
2818
+ Addedorderedmap@2.1.1(transitive)
+ Addedprosemirror-model@1.23.0(transitive)
- Removedorderedmap@1.1.8(transitive)
- Removedprosemirror-model@0.24.0(transitive)
Updatedprosemirror-model@^1.0.0