@atlaskit/editor-plugin-tasks-and-decisions
Version:
Tasks and decisions plugin for @atlaskit/editor-core
203 lines (202 loc) • 7.03 kB
JavaScript
import { uuid } from '@atlaskit/adf-schema';
import { getBlockMarkAttrs, getFirstParagraphBlockMarkAttrs } from '@atlaskit/editor-common/lists';
import { createBlockTaskItem, isTaskList } from '@atlaskit/editor-common/transforms';
import { Slice, Fragment } from '@atlaskit/editor-prosemirror/model';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
/**
* Transforms a paste slice to handle blockTaskItem nodes when pasting into task items.
*
* Initially we do not support user creation of blockTaskItem - it is intended primarily
* for TinyMCE migration purposes however that may change in the future.
* This function handles the behavior to work around Prosemirror behaviour which decides
* that blockTaskItem is the appropriate node to use here.
*
* @param slice - The slice being pasted
* @param view - The editor view where the paste is occurring
* @returns The transformed slice with blockTaskItems converted to taskItems when appropriate
*
* @example
* ```typescript
* const transformedSlice = tempTransformSliceToRemoveBlockTaskItem(pasteSlice, editorView);
* ```
*
* @see {@link https://hello.atlassian.net/wiki/spaces/EDITOR/pages/5626622054/Block+elements+in+task+-+Decision+log#Can-users-create-block-task-items%3F} for reasoning
*/
const getTaskPasteContext = view => {
var _schema$marks;
const {
schema,
selection
} = view.state;
const {
taskItem,
blockTaskItem,
paragraph
} = schema.nodes;
const fontSize = (_schema$marks = schema.marks) === null || _schema$marks === void 0 ? void 0 : _schema$marks.fontSize;
const {
$from
} = selection;
const currentParent = $from.parent;
const currentNode = typeof $from.node === 'function' ? $from.node() : undefined;
if ((currentParent === null || currentParent === void 0 ? void 0 : currentParent.type) === taskItem || (currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === taskItem) {
return {
isInTaskContext: true,
smallTextAttrs: false
};
}
if (!blockTaskItem) {
return {
isInTaskContext: false,
smallTextAttrs: false
};
}
if ((currentParent === null || currentParent === void 0 ? void 0 : currentParent.type) === blockTaskItem || (currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === blockTaskItem) {
return {
isInTaskContext: true,
smallTextAttrs: fontSize ? getFirstParagraphBlockMarkAttrs(currentParent !== null && currentParent !== void 0 ? currentParent : currentNode, fontSize) : false
};
}
if ((currentParent === null || currentParent === void 0 ? void 0 : currentParent.type) === paragraph && $from.depth > 0 && $from.node($from.depth - 1).type === blockTaskItem) {
return {
isInTaskContext: true,
smallTextAttrs: fontSize ? getBlockMarkAttrs(currentParent, fontSize) : false
};
}
return {
isInTaskContext: false,
smallTextAttrs: false
};
};
const convertTaskItemToBlockTaskItem = (node, schema, smallTextAttrs) => {
const {
fontSize
} = schema.marks;
return createBlockTaskItem({
attrs: node.attrs,
content: node.content,
marks: [fontSize.create(smallTextAttrs)],
schema
});
};
const addSmallTextToBlockTaskItem = (node, schema, smallTextAttrs) => {
const {
paragraph
} = schema.nodes;
const {
fontSize
} = schema.marks;
const newContent = [];
node.content.forEach(child => {
if (child.type === paragraph) {
newContent.push(paragraph.createChecked(child.attrs, child.content, child.marks.filter(mark => mark.type !== fontSize).concat(fontSize.create(smallTextAttrs))));
} else {
newContent.push(child);
}
});
return node.type.create(node.attrs, newContent);
};
const normalizeBlockTaskItemToTaskItems = (node, schema) => {
const {
taskItem,
blockTaskItem,
paragraph
} = schema.nodes;
if (!blockTaskItem || node.type !== blockTaskItem) {
return [node];
}
let allChildrenAreParagraphs = true;
node.content.forEach(child => {
if (child.type !== paragraph) {
allChildrenAreParagraphs = false;
}
});
if (allChildrenAreParagraphs && node.childCount > 0) {
const transformedContent = [];
node.content.forEach(paragraphNode => {
transformedContent.push(taskItem.create({
localId: uuid.generate(),
state: node.attrs.state || 'TODO'
}, paragraphNode.content));
});
return transformedContent;
}
return [node];
};
const transformSliceContent = (slice, transformNode) => {
const transformedContent = [];
slice.content.forEach(node => {
transformedContent.push(...transformNode(node));
});
if (transformedContent.length !== slice.content.childCount || transformedContent.some((node, idx) => node !== slice.content.child(idx))) {
const newFragment = Fragment.from(transformedContent);
return new Slice(newFragment, slice.openStart, slice.openEnd);
}
return slice;
};
const transformSliceToRemoveBlockTaskItemLegacy = (slice, view) => {
const {
schema
} = view.state;
const {
taskItem,
blockTaskItem
} = schema.nodes;
const isInTaskItem = view.state.selection.$from.node().type === taskItem;
if (!isInTaskItem || !blockTaskItem) {
return slice;
}
return transformSliceContent(slice, node => normalizeBlockTaskItemToTaskItems(node, schema));
};
export const normalizeNodeForTaskTextSize = (node, schema, smallTextAttrs) => {
const {
taskList,
taskItem,
blockTaskItem
} = schema.nodes;
const {
fontSize
} = schema.marks;
if (!smallTextAttrs) {
return [node];
}
if (isTaskList(node.type)) {
const transformedChildren = [];
node.content.forEach(child => {
transformedChildren.push(...normalizeNodeForTaskTextSize(child, schema, smallTextAttrs));
});
return [taskList.create(node.attrs, transformedChildren)];
}
if (blockTaskItem && fontSize) {
if (node.type === taskItem) {
return [convertTaskItemToBlockTaskItem(node, schema, smallTextAttrs)];
}
if (node.type === blockTaskItem) {
return [addSmallTextToBlockTaskItem(node, schema, smallTextAttrs)];
}
}
return [node];
};
const normalizeNodeForTaskPaste = (node, schema, smallTextAttrs) => {
if (smallTextAttrs) {
return normalizeNodeForTaskTextSize(node, schema, smallTextAttrs);
}
return normalizeBlockTaskItemToTaskItems(node, schema);
};
export const tempTransformSliceToRemoveBlockTaskItem = (slice, view) => {
const fontSizeExperimentEnabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true);
if (!fontSizeExperimentEnabled) {
return transformSliceToRemoveBlockTaskItemLegacy(slice, view);
}
const {
blockTaskItem
} = view.state.schema.nodes;
const {
isInTaskContext,
smallTextAttrs
} = getTaskPasteContext(view);
if (!isInTaskContext || !blockTaskItem) {
return slice;
}
return transformSliceContent(slice, node => normalizeNodeForTaskPaste(node, view.state.schema, smallTextAttrs));
};