Skip to content

Commit

Permalink
fix: Trigger characters not working after a new space(Shift+Enter)
Browse files Browse the repository at this point in the history
  • Loading branch information
iamsivin committed Sep 6, 2024
1 parent a0d8274 commit c475afc
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 84 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chatwoot/prosemirror-schema",
"version": "1.0.13",
"version": "1.0.14",
"description": "Schema setup for using prosemirror in chatwoot. Based on 👉 https://github.com/ProseMirror/prosemirror-example-setup/",
"main": "dist/index.js",
"scripts": {
Expand Down
169 changes: 86 additions & 83 deletions src/mentions/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { Decoration, DecorationSet } from 'prosemirror-view';

/**
* Creates a function to detect if the trigger character followed by a specified number of characters
* has been typed, starting from a new word or after a space.
* has been typed, starting from a new word, after a space, or after a newline.
* @param {string} char - The trigger character to detect.
* @param {number} [minChars=0] - The minimum number of characters that should follow the trigger character.
* @returns {Function} A function that takes a position object and returns true if the condition is met.
* @returns {Function} A function that takes a position object and returns the match if the condition is met.
*/
export const triggerCharacters = (char, minChars = 0) => $position => {
// Regular expression to find occurrences of 'char' followed by at least 'minChars' non-space characters.
// It matches these sequences starting from the beginning of the text or after a space.
const regexp = new RegExp(`(^|\\s)(${char}[^\\s${char}]{${minChars},})`, 'g');
const regexp = new RegExp(`(?:^)?${char}[^\\s${char}]{${minChars},}`, 'g');

// Get the position before the current cursor position in the document.
const textFrom = $position.before();
Expand All @@ -30,16 +30,21 @@ export const triggerCharacters = (char, minChars = 0) => $position => {

// eslint-disable-next-line
while ((match = regexp.exec(text))) {
const beforeChar = match[1]; // Will be empty at start of text, or a space in the middle
const fullMatch = match[2]; // Includes the trigger character and following text
// Check if the character before the match is a space, start of string, or null character
const prefix = match.input.slice(Math.max(0, match.index - 1), match.index);
if (!/^[\s\0]?$/.test(prefix)) {
// If the prefix is not empty, space, or null, skip this match
// eslint-disable-next-line
continue;
}

const from = match.index + $position.start() + beforeChar.length;
const to = from + fullMatch.length;
const from = match.index + $position.start();
const to = from + match[0].length;

if (from < $position.pos && to >= $position.pos) {
const trimmedText = fullMatch
? fullMatch.slice(char.length).trim()
: ""; // Remove trigger char and trim
const fullMatch = match[0];
// Remove trigger char and trim
const trimmedText = fullMatch ? fullMatch.slice(char.length) : '';
return { range: { from, to }, text: trimmedText };
}
}
Expand All @@ -53,96 +58,94 @@ export const suggestionsPlugin = ({
onChange = () => false,
onExit = () => false,
onKeyDown = () => false,
}) => {
return new Plugin({
key: new PluginKey('mentions'),
}) => new Plugin({
key: new PluginKey('mentions'),

view() {
return {
update: (view, prevState) => {
const prev = this.key.getState(prevState);
const next = this.key.getState(view.state);
view() {
return {
update: (view, prevState) => {
const prev = this.key.getState(prevState);
const next = this.key.getState(view.state);

const moved =
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 });
},
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,
};
},

state: {
init() {
return {
active: false,
range: {},
text: null,
};
},

apply(tr, prev) {
const { selection } = tr;
const next = { ...prev };
apply(tr, prev) {
const { selection } = tr;
const next = { ...prev };

if (selection.from === selection.to) {
if (
selection.from < prev.range.from ||
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;
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;
}

return next;
},
if (!next.active) {
next.range = {};
next.text = null;
}

return next;
},
},

props: {
handleKeyDown(view, event) {
const { active } = this.getState(view.state);
props: {
handleKeyDown(view, event) {
const { active } = this.getState(view.state);

if (!active) return false;
if (!active) return false;

return onKeyDown({ view, event });
},
decorations(editorState) {
const { active, range } = this.getState(editorState);
return onKeyDown({ view, event });
},
decorations(editorState) {
const { active, range } = this.getState(editorState);

if (!active) return null;
if (!active) return null;

return DecorationSet.create(editorState.doc, [
Decoration.inline(range.from, range.to, {
nodeName: 'span',
class: suggestionClass,
}),
]);
},
return DecorationSet.create(editorState.doc, [
Decoration.inline(range.from, range.to, {
nodeName: 'span',
class: suggestionClass,
}),
]);
},
});
};
},
});

0 comments on commit c475afc

Please sign in to comment.