@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
213 lines (207 loc) • 8.79 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.wholeSelectionHasMarks = exports.isMarkExcluded = exports.isMarkAllowedInRange = exports.anyMarkActive = void 0;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _model = require("@atlaskit/editor-prosemirror/model");
var _cellSelection = require("@atlaskit/editor-tables/cell-selection");
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
/**
* Determine if a mark of a specific type exists anywhere in the selection.
*/
var anyMarkActive = exports.anyMarkActive = function anyMarkActive(state, markType) {
var _state$selection = state.selection,
$from = _state$selection.$from,
from = _state$selection.from,
to = _state$selection.to,
empty = _state$selection.empty;
if (empty) {
return !!markType.isInSet(state.storedMarks || $from.marks());
}
var rangeHasMark = false;
if (state.selection instanceof _cellSelection.CellSelection) {
state.selection.forEachCell(function (cell, cellPos) {
var from = cellPos;
var to = cellPos + cell.nodeSize;
if (!rangeHasMark) {
rangeHasMark = state.doc.rangeHasMark(from, to, markType);
}
});
} else {
rangeHasMark = state.doc.rangeHasMark(from, to, markType);
}
return rangeHasMark;
};
var allMarksAreRuledOut = function allMarksAreRuledOut(marks) {
return Array.from(marks.values()).every(function (mark) {
return !mark;
});
};
/**
* Check if a selection contains any of the specifiged marks. The whole selection
* must have the mark for it to be considered active.
* @param state The editor state
* @param markTypes The marks to check for
* @returns A map indicating which marks are present in the selection
* @example
* const markTypes = editorState.schema.marks;
* const activeMarks = wholeSelectionHasMarks(editorState, [markTypes.strong, markTypes.em, markTypes.etc]);
*/
var wholeSelectionHasMarks = exports.wholeSelectionHasMarks = function wholeSelectionHasMarks(state, markTypes) {
var _state$selection2 = state.selection,
$from = _state$selection2.$from,
from = _state$selection2.from,
to = _state$selection2.to,
empty = _state$selection2.empty;
if (empty) {
return new Map(markTypes.map(function (markType) {
return [markType, !!markType.isInSet(state.storedMarks || $from.marks())];
}));
}
if (state.selection instanceof _cellSelection.CellSelection) {
return cellSelectionHasMarks(state.doc, state.selection, markTypes);
}
return wholeRangeHasMarks(from, to, state.doc, markTypes);
};
var cellSelectionHasMarks = function cellSelectionHasMarks(doc, selection, markTypes) {
// Warning: This is micro-optimized and is a total pain to actually read.
// Will attempt to explain in comments.
// Start with map of booleans for each mark
var cellsHaveMarks = new Map(markTypes.map(function (markType) {
return [markType, true];
}));
selection.forEachCell(function (cell, cellPos) {
// Early exit if all marks are already ruled out
if (allMarksAreRuledOut(cellsHaveMarks)) {
return;
}
var from = cellPos;
var to = cellPos + cell.nodeSize;
// On first cell just do a regular check
if (!cellsHaveMarks) {
cellsHaveMarks = wholeRangeHasMarks(from, to, doc, markTypes);
} else {
// Find the marks that are still true ie the ones that haven't been ruled out in
// previous cells. The idea here is to whittle down the list of marks so that we check
// less and less as we go, giving `wholeRangeHasMarks` more opportunities to exit early
var marksToCheck = [];
var _iterator = _createForOfIteratorHelper(cellsHaveMarks),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _step$value = (0, _slicedToArray2.default)(_step.value, 2),
markType = _step$value[0],
hasMark = _step$value[1];
if (hasMark) {
marksToCheck.push(markType);
}
}
// Look specifically for the marks that are not yet ruled out.
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
var cellHasMarks = wholeRangeHasMarks(from, to, doc, marksToCheck);
// Map these results back into the original array of results and repeat!
var _iterator2 = _createForOfIteratorHelper(cellHasMarks),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var _step2$value = (0, _slicedToArray2.default)(_step2.value, 2),
_markType = _step2$value[0],
_hasMark = _step2$value[1];
cellsHaveMarks.set(_markType, _hasMark);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
});
return cellsHaveMarks;
};
var wholeRangeHasMarks = function wholeRangeHasMarks(from, to, doc, markTypes) {
var hasMarks = new Map(markTypes.map(function (markType) {
return [markType, true];
}));
var hasNoMarks = new Map(markTypes.map(function (markType) {
return [markType, false];
}));
var isTextContent = false;
doc.nodesBetween(from, to, function (node) {
if (allMarksAreRuledOut(hasMarks)) {
// This won't be a true early exit, but will prevent diving into nodes and
// any checks further down the function.
return false;
}
if (!node.type.isText) {
return true; // continue traversing
}
isTextContent = true;
var _iterator3 = _createForOfIteratorHelper(hasMarks),
_step3;
try {
var _loop = function _loop() {
var _step3$value = (0, _slicedToArray2.default)(_step3.value, 2),
markType = _step3$value[0],
hasMark = _step3$value[1];
if (!hasMark) {
return 1; // continue
// already ruled out the mark, skip further checks
}
var value = markType instanceof _model.Mark ? markType.isInSet(node.marks) : node.marks.some(function (mark) {
return mark.type === markType;
});
hasMarks.set(markType, value);
};
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
if (_loop()) continue;
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
});
return isTextContent ? hasMarks : hasNoMarks;
};
var isMarkAllowedInRange = exports.isMarkAllowedInRange = function isMarkAllowedInRange(doc, ranges, type) {
var _loop2 = function _loop2() {
var _ranges$i = ranges[i],
$from = _ranges$i.$from,
$to = _ranges$i.$to;
var can = $from.depth === 0 ? doc.type.allowsMarkType(type) : false;
doc.nodesBetween($from.pos, $to.pos, function (node) {
if (can) {
return false;
}
can = node.inlineContent && node.type.allowsMarkType(type);
return;
});
if (can) {
return {
v: can
};
}
},
_ret;
for (var i = 0; i < ranges.length; i++) {
_ret = _loop2();
if (_ret) return _ret.v;
}
return false;
};
var isMarkExcluded = exports.isMarkExcluded = function isMarkExcluded(type, marks) {
if (marks) {
return marks.some(function (mark) {
return mark.type !== type && mark.type.excludes(type);
});
}
return false;
};