@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
355 lines • 14.8 kB
JavaScript
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