Browse Source

chore: Add support to mentions (#2)

main
Pranav Raj S 4 years ago committed by GitHub
parent
commit
45e4efcc15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 136
      src/mentions/plugin.js
  2. 72
      src/mentions/schema.js

136
src/mentions/plugin.js

@ -0,0 +1,136 @@
/**
* This file is a modified version of prosemirror-suggestions
* https://github.com/quartzy/prosemirror-suggestions/blob/master/src/suggestions.js
*/
import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
export const triggerCharacters = char => $position => {
const regexp = new RegExp(`(?:^)?${char}[^\\s${char}]*`, 'g');
const textFrom = $position.before();
const textTo = $position.end();
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
let match;
// eslint-disable-next-line
while ((match = regexp.exec(text))) {
const prefix = match.input.slice(Math.max(0, match.index - 1), match.index);
if (!/^[\s\0]?$/.test(prefix)) {
// eslint-disable-next-line
continue;
}
const from = match.index + $position.start();
let to = from + match[0].length;
if (from < $position.pos && to >= $position.pos) {
return { range: { from, to }, text: match[0] };
}
}
return null;
};
export const suggestionsPlugin = ({
matcher,
suggestionClass = 'prosemirror-mention-node',
onEnter = () => false,
onChange = () => false,
onExit = () => false,
onKeyDown = () => false,
}) => {
return new Plugin({
key: new PluginKey('mentions'),
view() {
return {
update: (view, prevState) => {
const prev = this.key.getState(prevState);
const next = this.key.getState(view.state);
const moved =
prev.active && next.active && prev.range.from !== next.range.from;
const started = !prev.active && next.active;
const stopped = prev.active && !next.active;
const changed = !started && !stopped && prev.text !== next.text;
if (stopped || moved)
onExit({ view, range: prev.range, text: prev.text });
if (changed && !moved)
onChange({ view, range: next.range, text: next.text });
if (started || moved)
onEnter({ view, range: next.range, text: next.text });
},
};
},
state: {
init() {
return {
active: false,
range: {},
text: null,
};
},
apply(tr, prev) {
const { selection } = tr;
const next = { ...prev };
if (selection.from === selection.to) {
if (
selection.from < prev.range.from ||
selection.from > prev.range.to
) {
next.active = false;
}
const $position = selection.$from;
const match = matcher($position);
if (match) {
next.active = true;
next.range = match.range;
next.text = match.text;
} else {
next.active = false;
}
} else {
next.active = false;
}
if (!next.active) {
next.range = {};
next.text = null;
}
return next;
},
},
props: {
handleKeyDown(view, event) {
const { active } = this.getState(view.state);
if (!active) return false;
return onKeyDown({ view, event });
},
decorations(editorState) {
const { active, range } = this.getState(editorState);
if (!active) return null;
return DecorationSet.create(editorState.doc, [
Decoration.inline(range.from, range.to, {
nodeName: 'span',
class: suggestionClass,
}),
]);
},
},
});
};

72
src/mentions/schema.js

@ -0,0 +1,72 @@
import {
schema,
MarkdownParser,
MarkdownSerializer,
} from 'prosemirror-markdown';
import { Schema } from 'prosemirror-model';
const mentionParser = () => ({
node: 'mention',
getAttrs: ({ mention }) => {
const { userId, userFullName } = mention;
return { userId, userFullName };
},
});
const markdownSerializer = () => (state, node) => {
const uri = state.esc(
`mention://user/${node.attrs.userId}/${node.attrs.userFullName}`
);
const escapedDisplayName = state.esc('@' + (node.attrs.userFullName || ''));
state.write(`[${escapedDisplayName}](${uri})`);
};
export const addMentionsToMarkdownSerializer = serializer =>
new MarkdownSerializer(
{ mention: markdownSerializer(), ...serializer.nodes },
serializer.marks
);
const mentionNode = {
attrs: { userFullName: { default: '' }, userId: { default: '' } },
group: 'inline',
inline: true,
selectable: true,
draggable: true,
atom: true,
toDOM: node => [
'span',
{
class: 'prosemirror-mention-node',
'mention-user-id': node.attrs.userId,
'mention-user-full-name': node.attrs.userFullName,
},
`@${node.attrs.userFullName}`,
],
parseDOM: [
{
tag: 'span[mention-user-id][mention-user-full-name]',
getAttrs: dom => {
const userId = dom.getAttribute('mention-user-id');
const userFullName = dom.getAttribute('mention-user-full-name');
return { userId, userFullName };
},
},
],
};
const addMentionNodes = nodes => nodes.append({ mention: mentionNode });
export const schemaWithMentions = new Schema({
nodes: addMentionNodes(schema.spec.nodes),
marks: schema.spec.marks,
});
export const addMentionsToMarkdownParser = parser => {
return new MarkdownParser(schemaWithMentions, parser.tokenizer, {
...parser.tokens,
mention: mentionParser(),
});
};
Loading…
Cancel
Save