@guardian/prosemirror-noting
Advanced tools
Comparing version 3.0.0 to 3.0.1
@@ -8,3 +8,5 @@ 'use strict'; | ||
var prosemirrorState = require('prosemirror-state'); | ||
var prosemirrorModel = require('prosemirror-model'); | ||
var uuid = _interopDefault(require('uuid/v1')); | ||
var v4 = _interopDefault(require('uuid/v4')); | ||
var prosemirrorView = require('prosemirror-view'); | ||
@@ -224,2 +226,152 @@ | ||
// which is expected to return a node - either the same one or a modified one - | ||
// which is then added in place of the old node | ||
var updateFragmentNodes = function updateFragmentNodes(updater) { | ||
return function (prevFrag) { | ||
var frag = prosemirrorModel.Fragment.empty; | ||
var appendNodeToFragment = function appendNodeToFragment(node) { | ||
return frag = frag.append(prosemirrorModel.Fragment.from(node)); | ||
}; | ||
prevFrag.forEach(function (node) { | ||
return appendNodeToFragment(node.copy(updateFragmentNodes(updater)(updater(node).content))); | ||
}); | ||
return frag; | ||
}; | ||
}; // Changes the attributes on a Mark or MarkType on a node if it exists on that | ||
// node | ||
var updateNodeMarkAttrs = function updateNodeMarkAttrs(node, mark) { | ||
var attrs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
return mark.isInSet(node.marks) ? node.mark(mark.removeFromSet(node.marks).concat(mark.type.create(Object.assign(mark.attrs, attrs)))) : node; | ||
}; // ensures that there are no notes in the document that have the same note id | ||
// in non-contiguous places, which would result in one large note between the | ||
// extremes of those places on certain edits | ||
// e.g. <note id="1">test</note> some <note id="1">stuff</note> | ||
// results in | ||
// e.g. <note id="1">test</note> some <note id="2">stuff</note> | ||
var sanitizeFragment = function sanitizeFragment(frag, markType) { | ||
var getId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : v4; | ||
var idMap = {}; // the current id of the node according to the input document | ||
var currentNoteId = null; | ||
var setNewId = function setNewId(prevId) { | ||
var newId = !idMap[prevId] ? prevId : getId(); | ||
idMap[prevId] = newId; | ||
currentNoteId = prevId; | ||
return newId; | ||
}; // This will return an updated id for this id depending on whether it's been | ||
// seen before in a previous non-contiguous note range, if it's been seen | ||
// before then a new id will be generated and used for this id while the range | ||
// is contiguous | ||
var getAdjustNoteId = function getAdjustNoteId(id) { | ||
if (id === currentNoteId) { | ||
return idMap[id]; | ||
} | ||
return setNewId(id); | ||
}; | ||
var closeNote = function closeNote() { | ||
currentNoteId = null; | ||
}; | ||
return updateFragmentNodes(function (node) { | ||
var noteMark = markType.isInSet(node.marks); | ||
if (noteMark) { | ||
return updateNodeMarkAttrs(node, noteMark, { | ||
id: getAdjustNoteId(noteMark.attrs.id) | ||
}); | ||
} // if we're in a text node and we don't have a noteMark then assume we are | ||
// not in a note and close the range | ||
if (node.isText) { | ||
closeNote(); | ||
} | ||
return node; | ||
})(frag); | ||
}; // Similar to sanitizeFragment but allows a node to be passed instead | ||
var sanitizeNode = function sanitizeNode(node, markType, getId) { | ||
return node.copy(sanitizeFragment(node.content, markType, getId)); | ||
}; // Return an array of all of the new ranges in a document [[start, end], ...] | ||
var getInsertedRanges = function getInsertedRanges(_ref) { | ||
var mapping = _ref.mapping; | ||
var ranges = []; | ||
mapping.maps.forEach(function (stepMap, i) { | ||
stepMap.forEach(function (oldStart, oldEnd, newStart, newEnd) { | ||
ranges.push([mapping.slice(i + 1).map(newStart), mapping.slice(i + 1).map(newEnd)]); | ||
}); | ||
}); | ||
return ranges; | ||
}; | ||
var charsAdded = function charsAdded(oldState, state) { | ||
return state.doc.textContent.length - oldState.doc.textContent.length; | ||
}; | ||
/* | ||
* This takes a doc node and a marktype and hunts for them (assuming the have an id | ||
* on their attrs) and merges their start and ends (for use with the note tracker) | ||
* | ||
* Unlike sanitizeNode, this will not look for contiguosness when finding the | ||
* notes as this helper assumes that the consuming code is not interested in | ||
* sanitizing the code. This should not pose any problems as long as notes | ||
* are getting sanitized on load and on paste. | ||
*/ | ||
var notesFromDoc = function notesFromDoc(doc, markType) { | ||
var min = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; | ||
var max = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
var notes = {}; | ||
var _ref2 = new prosemirrorState.AllSelection(doc), | ||
from = _ref2.from, | ||
to = _ref2.to; | ||
var _min = min === false ? from : min; | ||
var _max = max === false ? to : max; | ||
doc.nodesBetween(_min, _max, function (node, start) { | ||
var end = start + node.nodeSize; | ||
var mark = markType.isInSet(node.marks); | ||
if (mark) { | ||
var _mark$attrs = mark.attrs, | ||
id = _mark$attrs.id, | ||
meta = _mark$attrs.meta; | ||
notes[id] = notes[id] || { | ||
id: id, | ||
meta: meta, | ||
// this should be the same across all notes so just set it here | ||
nodes: [], | ||
start: Infinity, | ||
end: -Infinity | ||
}; | ||
notes[id] = Object.assign({}, notes[id], { | ||
start: Math.min(notes[id].start, start), | ||
end: Math.max(notes[id].end, end), | ||
nodes: _toConsumableArray(notes[id].nodes).concat([{ | ||
start: start, | ||
end: end | ||
}]) | ||
}); | ||
} | ||
}); | ||
return Object.keys(notes).map(function (id) { | ||
return notes[id]; | ||
}); | ||
}; | ||
var ensureType = function ensureType(meta) { | ||
@@ -419,18 +571,24 @@ if (!meta) { | ||
key: "rebuildRange", | ||
value: function rebuildRange(oldState, state) { | ||
var start = oldState.doc.content.findDiffStart(state.doc.content); | ||
value: function rebuildRange(state) { | ||
var ranges = getInsertedRanges(state); | ||
if (start) { | ||
var end = oldState.doc.content.findDiffEnd(state.doc.content).b; | ||
if (start < end) { | ||
return this.mergeableRange(start, end); | ||
} else if (oldState.doc.nodeSize < state.doc.nodeSize) { | ||
// make sure we're over-zealous with our rebuild size | ||
var diff = state.doc.nodeSize - oldState.doc.nodeSize; | ||
return this.mergeableRange(start, start + diff); | ||
} | ||
if (!ranges.length) { | ||
return false; | ||
} | ||
return false; | ||
var start = ranges.reduce(function (acc, _ref5) { | ||
var _ref6 = _slicedToArray(_ref5, 2), | ||
from = _ref6[0], | ||
to = _ref6[1]; | ||
return Math.min(acc, from, to); | ||
}, Infinity); | ||
var end = ranges.reduce(function (acc, _ref7) { | ||
var _ref8 = _slicedToArray(_ref7, 2), | ||
from = _ref8[0], | ||
to = _ref8[1]; | ||
return Math.max(acc, from, to); | ||
}, -Infinity); | ||
return start < end ? this.mergeableRange(start, end) : false; | ||
} | ||
@@ -442,54 +600,2 @@ }]); | ||
var charsAdded = function charsAdded(oldState, state) { | ||
return state.doc.textContent.length - oldState.doc.textContent.length; | ||
}; | ||
/* | ||
* This takes a doc node and a marktype and hunts for them (assuming the have an id | ||
* on their attrs) and merges their start and ends (for use with the note tracker) | ||
*/ | ||
var notesFromDoc = function notesFromDoc(doc, markType) { | ||
var min = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; | ||
var max = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
var notes = {}; | ||
var _ref = new prosemirrorState.AllSelection(doc), | ||
from = _ref.from, | ||
to = _ref.to; | ||
var _min = min === false ? from : min; | ||
var _max = max === false ? to : max; | ||
doc.nodesBetween(_min, _max, function (node, start) { | ||
var end = start + node.nodeSize; | ||
var mark = markType.isInSet(node.marks); | ||
if (mark) { | ||
var _mark$attrs = mark.attrs, | ||
id = _mark$attrs.id, | ||
meta = _mark$attrs.meta; | ||
notes[id] = notes[id] || { | ||
id: id, | ||
meta: meta, | ||
// this should be the same across all notes so just set it here | ||
nodes: [], | ||
start: Infinity, | ||
end: -Infinity | ||
}; | ||
notes[id] = Object.assign({}, notes[id], { | ||
start: Math.min(notes[id].start, start), | ||
end: Math.max(notes[id].end, end), | ||
nodes: _toConsumableArray(notes[id].nodes).concat([{ | ||
start: start, | ||
end: end | ||
}]) | ||
}); | ||
} | ||
}); | ||
return Object.keys(notes).map(function (id) { | ||
return notes[id]; | ||
}); | ||
}; | ||
var NoteTransaction = | ||
@@ -534,3 +640,3 @@ /*#__PURE__*/ | ||
} else if (tr.getMeta("paste") || tr.getMeta(this.historyPlugin)) { | ||
this.handlePaste(oldState); | ||
this.handlePaste(); | ||
} else { | ||
@@ -765,7 +871,7 @@ this.handleInput(oldState); | ||
key: "handlePaste", | ||
value: function handlePaste(oldState) { | ||
value: function handlePaste() { | ||
var noteTracker = this.noteTracker, | ||
tr = this.tr, | ||
markType = this.markType; | ||
var rebuildRange = noteTracker.rebuildRange(oldState, tr); | ||
var rebuildRange = noteTracker.rebuildRange(tr); | ||
@@ -1355,3 +1461,9 @@ if (rebuildRange) { | ||
decorations: noteDecorator, | ||
handleClick: handleClick && clickHandler(noteTracker, handleClick) | ||
handleClick: handleClick && clickHandler(noteTracker, handleClick), | ||
transformPasted: function transformPasted(_ref5) { | ||
var content = _ref5.content, | ||
openStart = _ref5.openStart, | ||
openEnd = _ref5.openEnd; | ||
return new prosemirrorModel.Slice(sanitizeFragment(content, markType), openStart, openEnd); | ||
} | ||
}, | ||
@@ -1376,1 +1488,2 @@ filterTransaction: function filterTransaction() { | ||
exports.toggleNote = toggleNote; | ||
exports.sanitizeNode = sanitizeNode; |
{ | ||
"name": "@guardian/prosemirror-noting", | ||
"version": "3.0.0", | ||
"version": "3.0.1", | ||
"description": "A plugin to allow noting in prosemirror", | ||
@@ -5,0 +5,0 @@ "main": "dist/noting.js", |
import { Plugin } from "prosemirror-state"; | ||
import { Slice } from "prosemirror-model"; | ||
import NoteTracker from "./NoteTracker"; | ||
@@ -6,3 +7,7 @@ import NoteTransaction from "./NoteTransaction"; | ||
import clickHandler from "./clickHandler"; | ||
import { notesFromDoc } from "./utils/StateUtils"; | ||
import { | ||
notesFromDoc, | ||
sanitizeNode, | ||
sanitizeFragment | ||
} from "./utils/StateUtils"; | ||
import { createNoteMark } from "./utils/SchemaUtils"; | ||
@@ -133,3 +138,5 @@ import SharedNoteStateTracker from "./SharedNoteStateTracker"; | ||
decorations: noteDecorator, | ||
handleClick: handleClick && clickHandler(noteTracker, handleClick) | ||
handleClick: handleClick && clickHandler(noteTracker, handleClick), | ||
transformPasted: ({ content, openStart, openEnd }) => | ||
new Slice(sanitizeFragment(content, markType), openStart, openEnd) | ||
}, | ||
@@ -148,2 +155,2 @@ filterTransaction: (...args) => | ||
export { createNoteMark, buildNoter, toggleNote }; | ||
export { createNoteMark, buildNoter, toggleNote, sanitizeNode }; |
import Note from "./Note"; | ||
import uuid from "uuid/v1"; | ||
import { cloneDeep } from "./utils/helpers"; | ||
import { getInsertedRanges } from "./utils/StateUtils"; | ||
@@ -157,17 +158,21 @@ const ensureType = meta => { | ||
rebuildRange(oldState, state) { | ||
const start = oldState.doc.content.findDiffStart(state.doc.content); | ||
rebuildRange(state) { | ||
let ranges = getInsertedRanges(state); | ||
if (start) { | ||
const end = oldState.doc.content.findDiffEnd(state.doc.content).b; | ||
if (start < end) { | ||
return this.mergeableRange(start, end); | ||
} else if (oldState.doc.nodeSize < state.doc.nodeSize) { | ||
// make sure we're over-zealous with our rebuild size | ||
const diff = state.doc.nodeSize - oldState.doc.nodeSize; | ||
return this.mergeableRange(start, start + diff); | ||
} | ||
if (!ranges.length) { | ||
return false; | ||
} | ||
return false; | ||
const start = ranges.reduce( | ||
(acc, [from, to]) => Math.min(acc, from, to), | ||
Infinity | ||
); | ||
const end = ranges.reduce( | ||
(acc, [from, to]) => Math.max(acc, from, to), | ||
-Infinity | ||
); | ||
return start < end ? this.mergeableRange(start, end) : false; | ||
} | ||
} |
@@ -34,3 +34,3 @@ import { Selection } from "prosemirror-state"; | ||
} else if (tr.getMeta("paste") || tr.getMeta(this.historyPlugin)) { | ||
this.handlePaste(oldState); | ||
this.handlePaste(); | ||
} else { | ||
@@ -249,5 +249,5 @@ this.handleInput(oldState); | ||
*/ | ||
handlePaste(oldState) { | ||
handlePaste() { | ||
const { noteTracker, tr, markType } = this; | ||
const rebuildRange = noteTracker.rebuildRange(oldState, tr); | ||
const rebuildRange = noteTracker.rebuildRange(tr); | ||
@@ -254,0 +254,0 @@ if (rebuildRange) { |
import { AllSelection } from "prosemirror-state"; | ||
import { Fragment } from "prosemirror-model"; | ||
import v4 from "uuid/v4"; | ||
// Runs through a Fragment's nodes and runs `updater` on them, | ||
// which is expected to return a node - either the same one or a modified one - | ||
// which is then added in place of the old node | ||
const updateFragmentNodes = updater => prevFrag => { | ||
let frag = Fragment.empty; | ||
const appendNodeToFragment = node => | ||
(frag = frag.append(Fragment.from(node))); | ||
prevFrag.forEach(node => | ||
appendNodeToFragment( | ||
node.copy(updateFragmentNodes(updater)(updater(node).content)) | ||
) | ||
); | ||
return frag; | ||
}; | ||
// Changes the attributes on a Mark or MarkType on a node if it exists on that | ||
// node | ||
const updateNodeMarkAttrs = (node, mark, attrs = {}) => | ||
mark.isInSet(node.marks) | ||
? node.mark( | ||
mark | ||
.removeFromSet(node.marks) | ||
.concat(mark.type.create(Object.assign(mark.attrs, attrs))) | ||
) | ||
: node; | ||
// ensures that there are no notes in the document that have the same note id | ||
// in non-contiguous places, which would result in one large note between the | ||
// extremes of those places on certain edits | ||
// e.g. <note id="1">test</note> some <note id="1">stuff</note> | ||
// results in | ||
// e.g. <note id="1">test</note> some <note id="2">stuff</note> | ||
export const sanitizeFragment = (frag, markType, getId = v4) => { | ||
let idMap = {}; | ||
// the current id of the node according to the input document | ||
let currentNoteId = null; | ||
const setNewId = prevId => { | ||
const newId = !idMap[prevId] ? prevId : getId(); | ||
idMap[prevId] = newId; | ||
currentNoteId = prevId; | ||
return newId; | ||
}; | ||
// This will return an updated id for this id depending on whether it's been | ||
// seen before in a previous non-contiguous note range, if it's been seen | ||
// before then a new id will be generated and used for this id while the range | ||
// is contiguous | ||
const getAdjustNoteId = id => { | ||
if (id === currentNoteId) { | ||
return idMap[id]; | ||
} | ||
return setNewId(id); | ||
}; | ||
const closeNote = () => { | ||
currentNoteId = null; | ||
}; | ||
return updateFragmentNodes(node => { | ||
const noteMark = markType.isInSet(node.marks); | ||
if (noteMark) { | ||
return updateNodeMarkAttrs(node, noteMark, { | ||
id: getAdjustNoteId(noteMark.attrs.id) | ||
}); | ||
} | ||
// if we're in a text node and we don't have a noteMark then assume we are | ||
// not in a note and close the range | ||
if (node.isText) { | ||
closeNote(); | ||
} | ||
return node; | ||
})(frag); | ||
}; | ||
// Similar to sanitizeFragment but allows a node to be passed instead | ||
export const sanitizeNode = (node, markType, getId) => | ||
node.copy(sanitizeFragment(node.content, markType, getId)); | ||
// Return an array of all of the new ranges in a document [[start, end], ...] | ||
export const getInsertedRanges = ({ mapping }) => { | ||
let ranges = []; | ||
mapping.maps.forEach((stepMap, i) => { | ||
stepMap.forEach((oldStart, oldEnd, newStart, newEnd) => { | ||
ranges.push([ | ||
mapping.slice(i + 1).map(newStart), | ||
mapping.slice(i + 1).map(newEnd) | ||
]); | ||
}); | ||
}); | ||
return ranges; | ||
}; | ||
export const charsAdded = (oldState, state) => | ||
@@ -7,5 +107,10 @@ state.doc.textContent.length - oldState.doc.textContent.length; | ||
/* | ||
* This takes a doc node and a marktype and hunts for them (assuming the have an id | ||
* on their attrs) and merges their start and ends (for use with the note tracker) | ||
*/ | ||
* This takes a doc node and a marktype and hunts for them (assuming the have an id | ||
* on their attrs) and merges their start and ends (for use with the note tracker) | ||
* | ||
* Unlike sanitizeNode, this will not look for contiguosness when finding the | ||
* notes as this helper assumes that the consuming code is not interested in | ||
* sanitizing the code. This should not pose any problems as long as notes | ||
* are getting sanitized on load and on paste. | ||
*/ | ||
export const notesFromDoc = (doc, markType, min = false, max = false) => { | ||
@@ -12,0 +117,0 @@ const notes = {}; |
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
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
2018701
25
2425
2