Skip to content

Feat/mention trigger #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
68 changes: 65 additions & 3 deletions src/sender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { twMerge } from "tailwind-merge";
import PublishNew from "./icons/publish-new.svg";
import QuickStop from "./icons/quick-stop.svg";
import type { Backend } from "./utils";
import { commandItems, serviceMentions } from "./utils/mentionItems";
import { List } from "./list";
import type { SelectOption } from "./list";

export interface InputCountProps extends React.ComponentProps<"span"> {
count: number;
Expand Down Expand Up @@ -120,7 +123,49 @@ export function Sender({
const textareaRef = useRef<HTMLTextAreaElement>(null);
const [message, setMessage] = useState(initialMessage);
const [isSending, setIsSending] = useState(false);
const [showCommandList, setShowCommandList] = useState(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be wrapped into a separate component? Also, could you please provide an image or video to show the effect?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I’ll give it a try. Here’s the current image showing the effect:
屏幕截图_8-8-2025_224122_127 0 0 1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moeyue23 I prefer the effects like GitHub comments:

image

Of course, if this difficult to achieve, you can also refer to https://x.ant.design/components/suggestion-cn. Although Ant Design is completely a opportunistic solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides, I think it might be better to use a lighter text color. :D

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I’ll try changing it! 🫠

const [showMentionList, setShowMentionList] = useState(false);
const renderList = (
show: boolean,
options: SelectOption[],
onSelected: (value: string) => void
) => {
if (!show) return null;

return (
<List
onMouseDown={(e) => e.preventDefault()}
className="absolute z-10 w-full max-h-60 overflow-y-auto bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg"
value={options.length > 0 ? options[0]?.value : undefined}
options={options}
onSelected={(value) => {
const textarea = textareaRef.current!;
const cursorPos = textarea.selectionStart;

const text = textarea.value;
const before = text.slice(0, cursorPos);
const after = text.slice(cursorPos);

const triggerIndex = Math.max(before.lastIndexOf('/'), before.lastIndexOf('@'));

if (triggerIndex === -1) return;

const triggerChar = before[triggerIndex];

const newText =
before.slice(0, triggerIndex) + triggerChar + value + ' ' + after;

textarea.value = newText;
textarea.selectionStart = textarea.selectionEnd = triggerIndex + value.length + 2;
textarea.focus();
setMessage(newText);

onSelected(value);
}
}
/>
);
};
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = "auto";
Expand Down Expand Up @@ -170,9 +215,24 @@ export function Sender({
);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);

const value = e.target.value;
setMessage(value);

const lastChar = value.length > 0 ? value[value.length - 1] : "";

if (lastChar === "/") {
setShowCommandList(true);
setShowMentionList(false);
} else if (lastChar === "@") {
setShowMentionList(true);
setShowCommandList(false);
} else {
setShowCommandList(false);
setShowMentionList(false);
}
},
[],
[]
);

return (
Expand Down Expand Up @@ -203,6 +263,8 @@ export function Sender({
className="ml-auto"
/>
</div>
{renderList(showCommandList, commandItems, () => setShowCommandList(false))}
{renderList(showMentionList, serviceMentions, () => setShowMentionList(false))}
</div>
);
}
}
12 changes: 12 additions & 0 deletions src/utils/mentionItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// slash command
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? It seems that these should be passed from the user.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might not be necessary, but idk how to do it properly, so I just followed the example from https://matechat.gitcode.com/components/mention/demo.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might not be necessary, but idk how to do it properly, so I just followed the example from https://matechat.gitcode.com/components/mention/demo.html

All contributors to MateChat React were interfered with by Vue's documentation in their judgment. We don't need to refer to the implementation and solutions of Vue. We just need to ensure that what Vue has, we also have. I think most of the time, the content of Vue documentation only tells us which components need to be implemented, and in fact, it has no other reference value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, idk how to implement it so that it’s passed from the user😂😂

It might not be necessary, but idk how to do it properly, so I just followed the example from https://matechat.gitcode.com/components/mention/demo.html这可能不是必要的,但我不知道如何正确地做,所以我就参考了 https://matechat.gitcode.com/components/mention/demo.html 的示例。

All contributors to MateChat React were interfered with by Vue's documentation in their judgment. We don't need to refer to the implementation and solutions of Vue. We just need to ensure that what Vue has, we also have. I think most of the time, the content of Vue documentation only tells us which components need to be implemented, and in fact, it has no other reference value.MateChat React 的所有贡献者在判断时都被 Vue 的文档干扰了。我们不需要参考 Vue 的实现和解决方案。我们只需要确保 Vue 有的东西,我们也有。我认为大多数情况下,Vue 文档的内容只是告诉我们需要实现哪些组件,实际上它并没有其他参考价值。

export const commandItems = [
{ label: "Write a story", value: "write a story" },
{ label: "Translate text", value: "translate text" },
{ label: "Explain code", value: "explain code" },
];

// @-mention services
export const serviceMentions = [
{ label: "MateChat", value: "matechat" },
{ label: "InsCode", value: "inscode" },
];