UNPKG

@atlaskit/editor-core

Version:

A package contains Atlassian editor core functionality

355 lines • 14.8 kB
import { isSpecialMention } from '@atlaskit/mention'; import { Plugin, Slice, Fragment } from '../../prosemirror'; import { inputRulePlugin } from './input-rules'; import { isMarkTypeAllowedAtCurrentPosition } from '../../utils'; import { analyticsService } from '../../analytics'; import mentionNodeView from './../../nodeviews/ui/mention'; import nodeViewFactory from '../../nodeviews/factory'; import keymapPlugin from './keymap'; import pluginKey from './plugin-key'; export var stateKey = pluginKey; var MentionsState = (function () { function MentionsState(state, providerFactory) { var _this = this; this.queryActive = false; this.enabled = true; this.onSelectPrevious = function () { return false; }; this.onSelectNext = function () { return false; }; this.onSelectCurrent = function () { return false; }; this.changeHandlers = []; this.handleProvider = function (name, provider) { switch (name) { case 'mentionProvider': _this.setMentionProvider(provider); break; } }; this.onMentionResult = function (mentions, query) { if (!query) { return; } if (query.length > 0 && query === _this.query) { _this.currentQueryResult = mentions; } var match = _this.findExactMatch(query, mentions); if (match) { _this.queryResults.set(query, match); } if (_this.isSubQueryOfCurrentQuery(query)) { _this.previousQueryResultCount = mentions.length; } }; this.changeHandlers = []; this.state = state; this.dirty = false; this.queryResults = new Map(); this.tokens = new Map(); this.previousQueryResultCount = -1; providerFactory.subscribe('mentionProvider', this.handleProvider); } MentionsState.prototype.subscribe = function (cb) { this.changeHandlers.push(cb); cb(this); }; MentionsState.prototype.unsubscribe = function (cb) { this.changeHandlers = this.changeHandlers.filter(function (ch) { return ch !== cb; }); }; MentionsState.prototype.apply = function (tr, state) { if (!this.mentionProvider) { return; } var mentionQuery = state.schema.marks.mentionQuery; var doc = state.doc, selection = state.selection; var from = selection.from, to = selection.to; this.dirty = false; var newEnabled = this.isEnabled(state); if (newEnabled !== this.enabled) { this.enabled = newEnabled; this.dirty = true; } var hasActiveQueryNode = function (node) { var mark = mentionQuery.isInSet(node.marks); return mark && mark.attrs.active; }; if (this.rangeHasNodeMatchingQuery(doc, from - 1, to, hasActiveQueryNode)) { if (!this.queryActive) { this.dirty = true; this.queryActive = true; } var nodeBefore = selection.$from.nodeBefore; var newQuery = (nodeBefore && nodeBefore.textContent || '').substr(1); if (this.query !== newQuery) { this.dirty = true; this.query = newQuery; if (newQuery.length === 0) { this.currentQueryResult = undefined; } } } else if (this.queryActive) { this.dirty = true; return; } }; MentionsState.prototype.update = function (state) { var _this = this; this.state = state; if (!this.mentionProvider) { return; } var newAnchorElement = this.view.dom.querySelector('[data-mention-query]'); if (newAnchorElement !== this.anchorElement) { this.dirty = true; this.anchorElement = newAnchorElement; } var mentionQuery = state.schema.marks.mentionQuery; var doc = state.doc, selection = state.selection; var from = selection.from, to = selection.to; if (!doc.rangeHasMark(from - 1, to, mentionQuery) && this.queryActive) { this.dismiss(); return; } if (this.dirty) { this.changeHandlers.forEach(function (cb) { return cb(_this); }); } }; MentionsState.prototype.rangeHasNodeMatchingQuery = function (doc, from, to, query) { var found = false; doc.nodesBetween(from, to, function (node) { if (query(node)) { found = true; } }); return found; }; MentionsState.prototype.dismiss = function () { var transaction = this.generateDismissTransaction(); if (transaction) { var view = this.view; view.dispatch(transaction); } return true; }; MentionsState.prototype.generateDismissTransaction = function (tr) { this.clearState(); var state = this.state; var currentTransaction = tr ? tr : state.tr; if (state) { var schema = state.schema; var markType = schema.mark('mentionQuery'); var _a = this.findActiveMentionQueryMark(), start = _a.start, end = _a.end; return currentTransaction .removeMark(start, end, markType) .removeStoredMark(markType); } return currentTransaction; }; MentionsState.prototype.isEnabled = function (state) { var currentState = state ? state : this.state; var mentionQuery = currentState.schema.marks.mentionQuery; return isMarkTypeAllowedAtCurrentPosition(mentionQuery, currentState); }; MentionsState.prototype.findMentionQueryMarks = function (active) { if (active === void 0) { active = true; } var state = this.state; var doc = state.doc, schema = state.schema; var mentionQuery = schema.marks.mentionQuery; var marks = []; doc.nodesBetween(0, doc.nodeSize - 2, function (node, pos) { var mark = mentionQuery.isInSet(node.marks); if (mark) { var query = node.textContent.substr(1).trim(); if ((active && mark.attrs.active) || (!active && !mark.attrs.active)) { marks.push({ start: pos, end: pos + node.textContent.length, query: query }); } return false; } return true; }); return marks; }; MentionsState.prototype.findActiveMentionQueryMark = function () { var activeMentionQueryMarks = this.findMentionQueryMarks(true); if (activeMentionQueryMarks.length !== 1) { return { start: -1, end: -1, query: '' }; } return activeMentionQueryMarks[0]; }; MentionsState.prototype.insertMention = function (mentionData, queryMark) { var view = this.view; view.dispatch(this.generateInsertMentionTransaction(mentionData, queryMark)); }; MentionsState.prototype.generateInsertMentionTransaction = function (mentionData, queryMark, tr) { var state = this.state; var mention = state.schema.nodes.mention; var currentTransaction = tr ? tr : state.tr; if (mention && mentionData) { var activeMentionQueryMark = this.findActiveMentionQueryMark(); var _a = queryMark ? queryMark : activeMentionQueryMark, start = _a.start, end = _a.end; var renderName = mentionData.nickname ? mentionData.nickname : mentionData.name; var nodes = [mention.create({ text: "@" + renderName, id: mentionData.id, accessLevel: mentionData.accessLevel })]; if (!this.isNextCharacterSpace(end, currentTransaction.doc)) { nodes.push(state.schema.text(' ')); } this.clearState(); var transaction = currentTransaction; if (activeMentionQueryMark.end !== end) { var mentionMark = state.schema.mark('mentionQuery', { active: true }); transaction = transaction.removeMark(end, activeMentionQueryMark.end, mentionMark); } transaction = transaction.replaceWith(start, end, nodes); return transaction; } else { return this.generateDismissTransaction(currentTransaction); } }; MentionsState.prototype.isNextCharacterSpace = function (position, doc) { try { var resolvedPosition = doc.resolve(position); return resolvedPosition.nodeAfter && resolvedPosition.nodeAfter.textContent.indexOf(' ') === 0; } catch (e) { return false; } }; MentionsState.prototype.setMentionProvider = function (provider) { var _this = this; return new Promise(function (resolve, reject) { if (provider && provider.then) { provider.then(function (mentionProvider) { if (_this.mentionProvider) { _this.mentionProvider.unsubscribe('editor-mentionpicker'); _this.currentQueryResult = undefined; } _this.mentionProvider = mentionProvider; _this.mentionProvider.subscribe('editor-mentionpicker', undefined, undefined, undefined, _this.onMentionResult); // Improve first mentions performance by establishing a connection and populating local search _this.mentionProvider.filter(''); resolve(mentionProvider); }) .catch(function () { _this.mentionProvider = undefined; }); } else { _this.mentionProvider = undefined; } }); }; MentionsState.prototype.trySelectCurrent = function () { var currentQuery = this.query ? this.query.trim() : ''; var mentions = this.currentQueryResult ? this.currentQueryResult : []; var mentionsCount = mentions.length; this.tokens.set(currentQuery, this.findActiveMentionQueryMark()); if (!this.mentionProvider) { return false; } var queryInFlight = this.mentionProvider.isFiltering(currentQuery); if (!queryInFlight && mentionsCount === 1) { analyticsService.trackEvent('atlassian.editor.mention.try.select.current'); this.onSelectCurrent(); return true; } // No results for the current query OR no results expected because previous subquery didn't return anything if ((!queryInFlight && mentionsCount === 0) || this.previousQueryResultCount === 0) { analyticsService.trackEvent('atlassian.editor.mention.try.insert.previous'); this.tryInsertingPreviousMention(); } if (!this.query) { this.dismiss(); } return false; }; MentionsState.prototype.tryInsertingPreviousMention = function () { var _this = this; var mentionInserted = false; this.tokens.forEach(function (value, key) { var match = _this.queryResults.get(key); if (match) { analyticsService.trackEvent('atlassian.editor.mention.insert.previous.match.success'); _this.insertMention(match, value); _this.tokens.delete(key); mentionInserted = true; } }); if (!mentionInserted) { analyticsService.trackEvent('atlassian.editor.mention.insert.previous.match.no.match'); this.dismiss(); } }; MentionsState.prototype.isSubQueryOfCurrentQuery = function (query) { return this.query && this.query.indexOf(query) === 0 && !this.mentionProvider.isFiltering(query); }; MentionsState.prototype.findExactMatch = function (query, mentions) { var filteredMentions = mentions.filter(function (mention) { if (mention.nickname && mention.nickname.toLocaleLowerCase() === query.toLocaleLowerCase()) { return mention; } }); if (filteredMentions.length > 1) { filteredMentions = filteredMentions.filter(function (mention) { return isSpecialMention(mention); }); } return filteredMentions.length === 1 ? filteredMentions[0] : null; }; MentionsState.prototype.clearState = function () { this.queryActive = false; this.query = undefined; this.tokens.clear(); this.previousQueryResultCount = -1; }; MentionsState.prototype.setView = function (view) { this.view = view; }; MentionsState.prototype.insertMentionQuery = function () { var state = this.view.state; var node = state.schema.text('@', [state.schema.mark('mentionQuery')]); this.view.dispatch(state.tr.replaceSelection(new Slice(Fragment.from(node), 0, 0))); if (!this.view.hasFocus()) { this.view.focus(); } }; return MentionsState; }()); export { MentionsState }; export function createPlugin(providerFactory) { return new Plugin({ state: { init: function (config, state) { return new MentionsState(state, providerFactory); }, apply: function (tr, prevPluginState, oldState, newState) { // NOTE: Don't replace the pluginState here. prevPluginState.apply(tr, newState); return prevPluginState; } }, props: { nodeViews: { mention: nodeViewFactory(providerFactory, { mention: mentionNodeView }), } }, key: pluginKey, view: function (view) { var pluginState = pluginKey.getState(view.state); pluginState.setView(view); return { update: function (view, prevState) { pluginState.update(view.state); }, destroy: function () { providerFactory.unsubscribe('mentionProvider', pluginState.handleProvider); } }; } }); } var plugins = function (schema, providerFactory) { return [createPlugin(providerFactory), inputRulePlugin(schema), keymapPlugin(schema)].filter(function (plugin) { return !!plugin; }); }; export default plugins; //# sourceMappingURL=index.js.map