UNPKG

@atlaskit/editor-plugin-status

Version:

Status plugin for @atlaskit/editor-core

171 lines 5.92 kB
import { uuid } from '@atlaskit/adf-schema'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { withAnalytics } from '@atlaskit/editor-common/editor-analytics'; import { getAnnotationMarksForPos } from '@atlaskit/editor-common/utils'; import { Fragment } from '@atlaskit/editor-prosemirror/model'; import { NodeSelection, Selection } from '@atlaskit/editor-prosemirror/state'; import { canInsert } from '@atlaskit/editor-prosemirror/utils'; import { fg } from '@atlaskit/platform-feature-flags'; import { pluginKey } from './plugin-key'; export const DEFAULT_STATUS = { text: '', color: 'neutral' }; export const DEFAULT_STATUS_NEW = { text: '', color: 'neutral', style: 'mixedCase' }; export const verifyAndInsertStatus = (statusNode, tr, annotationMarks) => { const fragment = Fragment.fromArray([statusNode, tr.doc.type.schema.text(' ', annotationMarks)]); const insertable = canInsert(tr.selection.$from, fragment); if (!insertable) { const parentSelection = NodeSelection.create(tr.doc, tr.selection.from - tr.selection.$anchor.parentOffset - 1); tr.insert(parentSelection.to, fragment).setSelection(NodeSelection.create(tr.doc, parentSelection.to + 1)); } else { tr.insert(tr.selection.from, fragment).setSelection(NodeSelection.create(tr.doc, tr.selection.from - fragment.size)); } return tr.setMeta(pluginKey, { showStatusPickerAt: tr.selection.from, isNew: true }).scrollIntoView(); }; export const createStatus = tr => { const annotationMarksForPos = getAnnotationMarksForPos(tr.selection.$head); const statusNode = tr.doc.type.schema.nodes.status.createChecked({ ...(fg('platform-dst-lozenge-tag-badge-visual-uplifts') ? DEFAULT_STATUS_NEW : DEFAULT_STATUS), localId: uuid.generate() }, null, annotationMarksForPos); return verifyAndInsertStatus(statusNode, tr, annotationMarksForPos); }; export const insertStatus = editorAnalyticsAPI => (inputMethod = INPUT_METHOD.TOOLBAR) => ({ tr }) => { const statusTr = createStatus(tr); editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({ action: ACTION.INSERTED, actionSubject: ACTION_SUBJECT.DOCUMENT, actionSubjectId: ACTION_SUBJECT_ID.STATUS, attributes: { inputMethod }, eventType: EVENT_TYPE.TRACK })(statusTr); return statusTr; }; export const updateStatus = status => (state, dispatch) => { const { schema } = state; const selectedStatus = status ? Object.assign(status, { text: status.text.trim(), localId: status.localId || uuid.generate() }) : status; const statusProps = { ...(fg('platform-dst-lozenge-tag-badge-visual-uplifts') ? DEFAULT_STATUS_NEW : DEFAULT_STATUS), ...selectedStatus }; let tr = state.tr; const { showStatusPickerAt } = pluginKey.getState(state) || {}; if (!showStatusPickerAt) { // Same behaviour as quick insert (used in createStatus) const statusNode = schema.nodes.status.createChecked(statusProps); tr = verifyAndInsertStatus(statusNode, state.tr); if (dispatch) { dispatch(tr); } return true; } if (state.doc.nodeAt(showStatusPickerAt)) { tr.setNodeMarkup(showStatusPickerAt, schema.nodes.status, statusProps).setSelection(NodeSelection.create(tr.doc, showStatusPickerAt)).setMeta(pluginKey, { showStatusPickerAt }).scrollIntoView(); if (dispatch) { dispatch(tr); } return true; } return false; }; export const updateStatusWithAnalytics = editorAnalyticsAPI => (inputMethod, status) => withAnalytics(editorAnalyticsAPI, { action: ACTION.INSERTED, actionSubject: ACTION_SUBJECT.DOCUMENT, actionSubjectId: ACTION_SUBJECT_ID.STATUS, attributes: { inputMethod }, eventType: EVENT_TYPE.TRACK })(updateStatus(status)); export const setStatusPickerAt = showStatusPickerAt => (state, dispatch) => { dispatch(state.tr.setMeta(pluginKey, { showStatusPickerAt, isNew: false })); return true; }; export const removeStatus = showStatusPickerAt => ({ tr }) => { tr.replace(showStatusPickerAt, showStatusPickerAt + 1); return tr; }; export const setFocusOnStatusInput = () => (state, dispatch) => { if (!dispatch) { return false; } const tr = state.tr.setMeta(pluginKey, { focusStatusInput: true }); dispatch(tr); return true; }; const handleClosingByArrows = (closingMethod, state, showStatusPickerAt, tr) => { if (closingMethod === 'arrowLeft') { // put cursor right before status Lozenge tr = tr.setSelection(Selection.near(state.tr.doc.resolve(showStatusPickerAt), -1)); } else if (closingMethod === 'arrowRight') { // put cursor right after status Lozenge tr = tr.setSelection(Selection.near(state.tr.doc.resolve(showStatusPickerAt + 1))); } }; export const commitStatusPicker = closingPayload => editorView => { const { state, dispatch } = editorView; const { showStatusPickerAt } = pluginKey.getState(state) || {}; const { closingMethod } = closingPayload || {}; if (!showStatusPickerAt) { return; } const statusNode = state.tr.doc.nodeAt(showStatusPickerAt); if (!statusNode) { return; } let tr = state.tr; tr = tr.setMeta(pluginKey, { showStatusPickerAt: null, focusStatusInput: false, isNew: false }); if (closingMethod) { handleClosingByArrows(closingMethod, state, showStatusPickerAt, tr); } else if (statusNode.attrs.text) { // still has content - keep content // move selection after status if selection did not change if (tr.selection.from === showStatusPickerAt) { tr = tr.setSelection(Selection.near(state.tr.doc.resolve(showStatusPickerAt + 2))); } } else { // no content - remove node tr = tr.delete(showStatusPickerAt, showStatusPickerAt + 1); } dispatch(tr); editorView.focus(); };