Skip to content
This repository has been archived by the owner on Jan 2, 2025. It is now read-only.

Add more try/catch blocks to autocomplete input #1187

Merged
merged 1 commit into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions client/src/components/Chat/ChatFooter/Input/InputCore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import { schema as basicSchema } from 'prosemirror-schema-basic';
import * as icons from 'file-icons-js';
import { useTranslation } from 'react-i18next';
import { getFileExtensionForLang, InputEditorContent } from '../../../../utils';
import {
ParsedQueryType,
ParsedQueryTypeEnum,
} from '../../../../types/general';
import { ParsedQueryType } from '../../../../types/general';
import { getMentionsPlugin } from './mentionPlugin';
import { addMentionNodes, mapEditorContentToInputValue } from './utils';
import { placeholderPlugin } from './placeholderPlugin';
Expand Down
215 changes: 117 additions & 98 deletions client/src/components/Chat/ChatFooter/Input/mentionPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,49 @@ export function getMatch(
allowSpace?: boolean;
},
) {
// take current para text content upto cursor start.
// this makes the regex simpler and parsing the matches easier.
const parastart = $position.before();
const text = $position.doc.textBetween(parastart, $position.pos, '\n', '\0');

const regex = getRegexp(opts.mentionTrigger, opts.allowSpace);

const match = text.match(regex);

// if match found, return match with useful information.
if (match) {
// adjust match.index to remove the matched extra space
match.index =
match[0].startsWith(' ') || match[0].startsWith(insertAfterSelect)
? (match.index || 0) + 1
: match.index;
match[0] =
match[0].startsWith(' ') || match[0].startsWith(insertAfterSelect)
? match[0].substring(1, match[0].length)
: match[0];

// The absolute position of the match in the document
const from = $position.start() + match.index!;
const to = from + match[0].length;

const queryText = match[2];

return {
range: { from: from, to: to },
queryText: queryText,
type: 'mention',
};
try {
// take current para text content upto cursor start.
// this makes the regex simpler and parsing the matches easier.
const parastart = $position.before();
const text = $position.doc.textBetween(
parastart,
$position.pos,
'\n',
'\0',
);

const regex = getRegexp(opts.mentionTrigger, opts.allowSpace);

const match = text.match(regex);

// if match found, return match with useful information.
if (match) {
// adjust match.index to remove the matched extra space
match.index =
match[0].startsWith(' ') || match[0].startsWith(insertAfterSelect)
? (match.index || 0) + 1
: match.index;
match[0] =
match[0].startsWith(' ') || match[0].startsWith(insertAfterSelect)
? match[0].substring(1, match[0].length)
: match[0];

// The absolute position of the match in the document
const from = $position.start() + match.index!;
const to = from + match[0].length;

const queryText = match[2];

return {
range: { from: from, to: to },
queryText: queryText,
type: 'mention',
};
}
// else if no match don't return anything.
} catch (e) {
console.log(e);
}
// else if no match don't return anything.
}

/**
Expand Down Expand Up @@ -150,55 +159,61 @@ export function getMentionsPlugin(opts: Partial<Options>) {
suggestions: Record<string, string>[],
opts: Options,
) {
el.innerHTML = opts.getSuggestionsHTML(suggestions, state.type);

// attach new item event handlers
el.querySelectorAll('.suggestion-item').forEach(function (itemNode, index) {
itemNode.addEventListener('click', function () {
select(view, state, opts);
view.focus();
});
// TODO: setIndex() needlessly queries.
// We already have the itemNode. SHOULD OPTIMIZE.
itemNode.addEventListener('mouseover', function () {
setIndex(index, state, opts);
});
itemNode.addEventListener('mouseout', function () {
setIndex(index, state, opts);
});
});

// highlight first element by default - like Facebook.
addClassAtIndex(state.index, opts.activeClass);

// TODO: knock off domAtPos usage. It's not documented and is not officially a public API.
// It's used currently, only to optimize the the query for textDOM
const node = view.domAtPos(view.state.selection.$from.pos);
const paraDOM = node.node;
const textDOM = (paraDOM as HTMLElement).querySelector(
'.' + opts.suggestionTextClass,
);

const offset = textDOM?.getBoundingClientRect();

document.body.appendChild(el);
el.classList.add('suggestion-item-container');
el.style.position = 'fixed';
el.style.left = -9999 + 'px';
const offsetLeft = offset?.left || 0;
setTimeout(() => {
el.style.left =
offsetLeft + el.clientWidth < window.innerWidth
? offsetLeft + 'px'
: offsetLeft +
(window.innerWidth - (offsetLeft + el.clientWidth) - 10) +
'px';
}, 10);

const bottom = window.innerHeight - (offset?.top || 0);
el.style.bottom = bottom + 'px';
el.style.display = 'block';
el.style.zIndex = '999999';
try {
el.innerHTML = opts.getSuggestionsHTML(suggestions, state.type);

// attach new item event handlers
el.querySelectorAll('.suggestion-item').forEach(
function (itemNode, index) {
itemNode.addEventListener('click', function () {
select(view, state, opts);
view.focus();
});
// TODO: setIndex() needlessly queries.
// We already have the itemNode. SHOULD OPTIMIZE.
itemNode.addEventListener('mouseover', function () {
setIndex(index, state, opts);
});
itemNode.addEventListener('mouseout', function () {
setIndex(index, state, opts);
});
},
);

// highlight first element by default - like Facebook.
addClassAtIndex(state.index, opts.activeClass);

// TODO: knock off domAtPos usage. It's not documented and is not officially a public API.
// It's used currently, only to optimize the the query for textDOM
const node = view.domAtPos(view.state.selection.$from.pos);
const paraDOM = node.node;
const textDOM = (paraDOM as HTMLElement).querySelector(
'.' + opts.suggestionTextClass,
);

const offset = textDOM?.getBoundingClientRect();

document.body.appendChild(el);
el.classList.add('suggestion-item-container');
el.style.position = 'fixed';
el.style.left = -9999 + 'px';
const offsetLeft = offset?.left || 0;
setTimeout(() => {
el.style.left =
offsetLeft + el.clientWidth < window.innerWidth
? offsetLeft + 'px'
: offsetLeft +
(window.innerWidth - (offsetLeft + el.clientWidth) - 10) +
'px';
}, 10);

const bottom = window.innerHeight - (offset?.top || 0);
el.style.bottom = bottom + 'px';
el.style.display = 'block';
el.style.zIndex = '999999';
} catch (e) {
console.log(e);
}
};

const hideList = function () {
Expand Down Expand Up @@ -269,25 +284,29 @@ export function getMentionsPlugin(opts: Partial<Options>) {
},

apply(tr, state) {
// compute state.active for current transaction and return
const newState = getNewState();
const selection = tr.selection;
if (selection.from !== selection.to) {
return newState;
}
try {
// compute state.active for current transaction and return
const newState = getNewState();
const selection = tr.selection;
if (selection.from !== selection.to) {
return newState;
}

const $position = selection.$from;
const match = getMatch($position, options);
const $position = selection.$from;
const match = getMatch($position, options);

// if match found update state
if (match) {
newState.active = true;
newState.range = match.range;
newState.type = match.type!;
newState.text = match.queryText;
}
// if match found update state
if (match) {
newState.active = true;
newState.range = match.range;
newState.type = match.type!;
newState.text = match.queryText;
}

return newState;
return newState;
} catch (e) {
return state;
}
},
},

Expand Down