@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
237 lines (226 loc) • 10.3 kB
JavaScript
export const isReferencedSource = (state, node) => {
var _node$attrs, _node$marks, _node$marks$find, _node$marks$find$attr;
if (!node) {
return false;
}
let found = false;
// Handle nodes having 2 uuids. They could have a localId or a fragment. Regardless this needs
// to check if either id is used by a data consumer.
const localIds = new Set([(_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.localId, (_node$marks = node.marks) === null || _node$marks === void 0 ? void 0 : (_node$marks$find = _node$marks.find(mark => mark.type === state.schema.marks.fragment)) === null || _node$marks$find === void 0 ? void 0 : (_node$marks$find$attr = _node$marks$find.attrs) === null || _node$marks$find$attr === void 0 ? void 0 : _node$marks$find$attr.localId].filter(Boolean));
// If there are no uuids on the node then it's not possible for it to be referenced anywhere.
if (!localIds.size) {
return false;
}
state.doc.descendants(node => {
var _dataConsumer$attrs$s, _dataConsumer$attrs$s2;
if (found) {
return false;
}
const dataConsumer = node.marks.find(mark => mark.type === state.schema.marks.dataConsumer);
if (!dataConsumer) {
return true;
}
found = (_dataConsumer$attrs$s = (_dataConsumer$attrs$s2 = dataConsumer.attrs.sources) === null || _dataConsumer$attrs$s2 === void 0 ? void 0 : _dataConsumer$attrs$s2.some(src => localIds.has(src))) !== null && _dataConsumer$attrs$s !== void 0 ? _dataConsumer$attrs$s : false;
return !found;
});
return found;
};
export const getConnections = state => {
const result = {};
const {
doc,
schema
} = state;
// Keeps a map of all raw ids -> to their preferred normalised varient. A node with both fragmentMark & localId will
// have both ids mapped here to the same normalized version.
const normalizedIds = new Map();
const dataConsumerSources = new Map();
// Perform a prelim scan creating the initial id to connection link mappings.
// This will also save a list of data sources consumed per node.
doc.descendants((node, pos) => {
var _node$attrs2, _fragmentMark$attrs$l, _fragmentMark, _fragmentMark$attrs, _node$attrs3, _fragmentMark2, _fragmentMark2$attrs, _node$attrs4, _fragmentMark3, _fragmentMark3$attrs;
let dataConsumer;
let fragmentMark;
node.marks.some(mark => {
if (mark.type === schema.marks.dataConsumer) {
dataConsumer = mark;
} else if (mark.type === schema.marks.fragment) {
fragmentMark = mark;
}
// Stop searching marks if we've found both consumer and fragment.
return !!dataConsumer && !!fragmentMark;
});
// If node cannot be referenced by any means then abort.
if (!fragmentMark && !((_node$attrs2 = node.attrs) !== null && _node$attrs2 !== void 0 && _node$attrs2.localId)) {
return true;
}
const normalizedId = (_fragmentMark$attrs$l = (_fragmentMark = fragmentMark) === null || _fragmentMark === void 0 ? void 0 : (_fragmentMark$attrs = _fragmentMark.attrs) === null || _fragmentMark$attrs === void 0 ? void 0 : _fragmentMark$attrs.localId) !== null && _fragmentMark$attrs$l !== void 0 ? _fragmentMark$attrs$l : (_node$attrs3 = node.attrs) === null || _node$attrs3 === void 0 ? void 0 : _node$attrs3.localId;
if (!normalizedId) {
return true;
}
if (!!((_fragmentMark2 = fragmentMark) !== null && _fragmentMark2 !== void 0 && (_fragmentMark2$attrs = _fragmentMark2.attrs) !== null && _fragmentMark2$attrs !== void 0 && _fragmentMark2$attrs.localId)) {
normalizedIds.set(fragmentMark.attrs.localId, normalizedId);
}
if (!!((_node$attrs4 = node.attrs) !== null && _node$attrs4 !== void 0 && _node$attrs4.localId)) {
normalizedIds.set(node.attrs.localId, normalizedId);
}
if (!!result[normalizedId]) {
// Duplicate ID has been found, we'll care about the first one for now.
return true;
}
result[normalizedId] = {
localId: normalizedId,
name: (_fragmentMark3 = fragmentMark) === null || _fragmentMark3 === void 0 ? void 0 : (_fragmentMark3$attrs = _fragmentMark3.attrs) === null || _fragmentMark3$attrs === void 0 ? void 0 : _fragmentMark3$attrs.name,
node,
pos,
targets: []
};
if (!!dataConsumer && dataConsumer.attrs.sources.length) {
dataConsumerSources.set(normalizedId, dataConsumer.attrs.sources);
}
// Do not descend into children of a node which has a dataConsumer attached. This assumes all children of the node cannot
// be referenced by another node.
return !dataConsumer;
});
// This 2nd-pass only looks at the consumer sources and updates all connections with the correct id refs.
for (const [localId, sources] of dataConsumerSources) {
// This is a ref to the node (connection link) which contains the consumer.
sources.forEach(src => {
var _normalizedIds$get;
const normalizedId = (_normalizedIds$get = normalizedIds.get(src)) !== null && _normalizedIds$get !== void 0 ? _normalizedIds$get : src;
const srcLink = result[normalizedId];
if (srcLink && normalizedId !== localId) {
srcLink.targets.push(localId);
}
});
}
return result;
};
export const removeConnectedNodes = (state, node) => {
if (!node) {
return state.tr;
}
const selectedLocalIds = getSelectedLocalIds(state, node);
const allNodes = getConnections(state);
const idsToBeDeleted = getIdsToBeDeleted(selectedLocalIds, allNodes);
if (!(idsToBeDeleted !== null && idsToBeDeleted !== void 0 && idsToBeDeleted.length)) {
return state.tr;
}
const {
tr
} = state;
let newTr = tr;
idsToBeDeleted.forEach(id => {
if (!allNodes[id]) {
return;
}
const {
node,
pos
} = allNodes[id];
newTr = newTr.delete(newTr.mapping.map(pos), newTr.mapping.map(node.nodeSize + pos));
});
return newTr;
};
// find all ids need to be remove connected to selected extension
const getIdsToBeDeleted = (selectedIds, allNodes) => {
if (!selectedIds.size) {
return [];
}
let searchSet = [...selectedIds];
const deletedIds = new Set();
while (searchSet.length) {
const id = searchSet.pop();
if (allNodes[id]) {
var _allNodes$id$targets$, _allNodes$id;
deletedIds.add(allNodes[id].localId);
searchSet = searchSet.concat((_allNodes$id$targets$ = (_allNodes$id = allNodes[id]) === null || _allNodes$id === void 0 ? void 0 : _allNodes$id.targets.filter(targetId => {
var _allNodes$targetId;
return !deletedIds.has((_allNodes$targetId = allNodes[targetId]) === null || _allNodes$targetId === void 0 ? void 0 : _allNodes$targetId.localId);
})) !== null && _allNodes$id$targets$ !== void 0 ? _allNodes$id$targets$ : []);
}
}
return Array.from(deletedIds);
};
// for get children info for confirmation dialog
export const getChildrenInfo = (state, node) => {
let allChildrenHadName = true;
if (!node) {
return [];
}
const childrenIdSet = new Set();
const childrenInfoArray = [];
const allNodes = getConnections(state);
const selectedNodeIds = getSelectedLocalIds(state, node);
selectedNodeIds.forEach(id => {
if (allNodes[id]) {
allNodes[id].targets.forEach(childrenIdSet.add, childrenIdSet);
}
});
for (const id of childrenIdSet) {
if (!getNodeNameById(id, allNodes)) {
allChildrenHadName = false;
break;
} else {
childrenInfoArray.push({
id,
name: getNodeNameById(id, allNodes),
amount: getChildrenNodeAmount(id, allNodes)
});
}
}
return allChildrenHadName ? childrenInfoArray : [];
};
const getChildrenNodeAmount = (id, allNodes) => {
const searchTerms = new Set([id]);
const traverseHistory = new Map();
const childrenIds = new Set();
traverseHistory.set(id, false);
while (searchTerms.size > 0) {
const [currTerm] = searchTerms;
let targets = getNodeTargetsById(currTerm, allNodes);
targets.forEach(target => {
const isTargetCounted = traverseHistory.get(target);
if (!isTargetCounted) {
searchTerms.add(target);
childrenIds.add(target);
}
target !== id && childrenIds.add(target);
});
traverseHistory.set(currTerm, true);
searchTerms.delete(currTerm);
}
return childrenIds.size;
};
const getNodeTargetsById = (id, allNodes) => {
if (!id || !allNodes[id]) {
return [];
}
return allNodes[id].targets;
};
const getNodeNameById = (id, allNodes) => {
if (typeof id === 'object') {
let name;
id.forEach(localId => {
var _name, _allNodes$localId;
name = (_name = name) !== null && _name !== void 0 ? _name : (_allNodes$localId = allNodes[localId]) === null || _allNodes$localId === void 0 ? void 0 : _allNodes$localId.name;
});
return name || null;
}
if (!id || !allNodes[id]) {
return null;
}
return allNodes[id].name;
};
const getSelectedLocalIds = (state, node) => {
var _node$attrs5, _node$marks2, _node$marks2$find, _node$marks2$find$att;
if (!node) {
return new Set([]);
}
const localIds = new Set([(_node$attrs5 = node.attrs) === null || _node$attrs5 === void 0 ? void 0 : _node$attrs5.localId, (_node$marks2 = node.marks) === null || _node$marks2 === void 0 ? void 0 : (_node$marks2$find = _node$marks2.find(mark => mark.type === state.schema.marks.fragment)) === null || _node$marks2$find === void 0 ? void 0 : (_node$marks2$find$att = _node$marks2$find.attrs) === null || _node$marks2$find$att === void 0 ? void 0 : _node$marks2$find$att.localId].filter(Boolean));
return localIds;
};
export const getNodeName = (state, node) => {
var _node$marks$find$attr2, _node$marks3, _node$marks3$find, _node$marks3$find$att;
return (_node$marks$find$attr2 = node === null || node === void 0 ? void 0 : (_node$marks3 = node.marks) === null || _node$marks3 === void 0 ? void 0 : (_node$marks3$find = _node$marks3.find(mark => mark.type === state.schema.marks.fragment)) === null || _node$marks3$find === void 0 ? void 0 : (_node$marks3$find$att = _node$marks3$find.attrs) === null || _node$marks3$find$att === void 0 ? void 0 : _node$marks3$find$att.name) !== null && _node$marks$find$attr2 !== void 0 ? _node$marks$find$attr2 : '';
};