@atlaskit/editor-plugin-custom-autoformat
Version:
Custom autoformat plugin for @atlaskit/editor-core
90 lines (82 loc) • 3.37 kB
JavaScript
import { processRawValue } from '@atlaskit/editor-common/utils';
import { closeHistory } from '@atlaskit/prosemirror-history';
import { autoformatAction } from './utils';
export const buildHandler = (_regex, handler) => {
return async (view, match, start, end) => {
const replacementPromise = handler(match.slice(1, match.length - 1));
// queue the position and match pair so that we can remap across transactions
// while we wait for the replacmentPromise to resolve
view.dispatch(autoformatAction(view.state.tr, {
action: 'matched',
match: match,
start,
end
}));
// ask the provider to give us an ADF node to replace the text with
const replacementData = await replacementPromise;
const replacementNode = processRawValue(view.state.schema, replacementData);
view.dispatch(autoformatAction(view.state.tr, {
action: 'resolved',
matchString: match[0],
replacement: replacementNode
}));
return replacementData;
};
};
/**
* Shift enter adds an Object Replacement Character (/ufffc) after
* the first word in the soft break line. Text between replaces this
* with a '|' as it's a non-text node. We still want to replaceWith
* on a string starting with an Object Replacement Character.
*/
export const isSoftBreakMatch = (docText, match) => {
const REPLACEMENT_CHARACTER = '\ufffc';
const docTextStartChar = docText[0];
const docTextEndSlice = docText.slice(1);
const matchStartChar = match[0][0];
const matchEndSlice = match[0].slice(1);
const slicesMatch = docTextEndSlice === matchEndSlice;
const isAlternator = docTextStartChar === '|';
const isObjReplaceChar = matchStartChar === REPLACEMENT_CHARACTER;
return slicesMatch && isAlternator && isObjReplaceChar;
};
export const completeReplacements = (view, state) => {
const {
inlineCard
} = view.state.schema.nodes;
state.matches.forEach(completedMatch => {
const matchingRequests = state.resolving.filter(candidate => candidate.match[0] === completedMatch.matchString);
let tr = view.state.tr;
matchingRequests.forEach(request => {
const {
match,
start,
end
} = request;
const {
replacement
} = completedMatch;
const prefix = match[1];
const suffix = match[match.length - 1];
const matchEndPos = end + suffix.length;
// only permit inlineCard as replacement target for now
if (!replacement || replacement.type !== inlineCard && !replacement.isText) {
return;
}
// get the current document text, adding # or | if we cross node boundaries
const docText = view.state.doc.textBetween(start, matchEndPos, '#', '|');
const canAutoformatAfterSoftbreak = isSoftBreakMatch(docText, match);
// only replace if text still remains the same as when typed at the start
if (docText === match[0] || canAutoformatAfterSoftbreak) {
tr = tr.replaceWith(tr.mapping.map(start + prefix.length), tr.mapping.map(end, -1), replacement);
}
});
// clear this match from plugin state now that we've processed it
tr = autoformatAction(tr, {
action: 'finish',
matchString: completedMatch.matchString
});
// and dispatch the replacement, closing history for cmd+z to allow undo separately
view.dispatch(closeHistory(tr));
});
};