diff --git a/lib/package.json b/lib/package.json
index b57b78f9..ca7ad432 100644
--- a/lib/package.json
+++ b/lib/package.json
@@ -111,6 +111,7 @@
"@tiptap/pm": "^3.6.5",
"@tiptap/react": "^3.13.0",
"@tiptap/starter-kit": "^3.13.0",
+ "@tiptap/suggestion": "^3.15.3",
"axios": "^1.13.2",
"browser-image-compression": "^2.0.2",
"classnames": "^2.5.1",
@@ -130,6 +131,7 @@
"react-qr-code": "^2.0.16",
"react-toastify": "^9.1.3",
"remark-breaks": "^4.0.0",
+ "tippy.js": "^6.3.7",
"tiptap-markdown": "^0.9.0",
"yet-another-react-lightbox": "^3.28.0"
},
diff --git a/lib/src/Components/Input/RichTextEditor.tsx b/lib/src/Components/Input/RichTextEditor.tsx
index c2dba635..fcf68233 100644
--- a/lib/src/Components/Input/RichTextEditor.tsx
+++ b/lib/src/Components/Input/RichTextEditor.tsx
@@ -4,11 +4,15 @@ import { Link } from '@tiptap/extension-link'
import { Placeholder } from '@tiptap/extension-placeholder'
import { EditorContent, useEditor } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
-import { useEffect } from 'react'
+import { useEffect, useMemo } from 'react'
import { Markdown } from 'tiptap-markdown'
-import { VideoEmbed } from '#components/TipTap/extensions/VideoEmbed'
-import { preprocessVideoLinks } from '#components/TipTap/utils/preprocessMarkdown'
+import { useGetItemColor } from '#components/Map/hooks/useItemColor'
+import { useItems } from '#components/Map/hooks/useItems'
+import { useAddTag, useTags } from '#components/Map/hooks/useTags'
+import { Hashtag, ItemMention, VideoEmbed } from '#components/TipTap/extensions'
+import { createHashtagSuggestion, createItemMentionSuggestion } from '#components/TipTap/extensions'
+import { preprocessMarkdown } from '#components/TipTap/utils/preprocessMarkdown'
import { InputLabel } from './InputLabel'
import { TextEditorMenu } from './TextEditorMenu'
@@ -42,6 +46,18 @@ export function RichTextEditor({
showMenu = true,
updateFormValue,
}: RichTextEditorProps) {
+ const tags = useTags()
+ const addTag = useAddTag()
+ const items = useItems()
+ const getItemColor = useGetItemColor()
+
+ // Memoize suggestion configurations to prevent unnecessary re-renders
+ const hashtagSuggestion = useMemo(() => createHashtagSuggestion(tags, addTag), [tags, addTag])
+ const itemMentionSuggestion = useMemo(
+ () => createItemMentionSuggestion(items, getItemColor),
+ [items, getItemColor],
+ )
+
const handleChange = () => {
let newValue: string | undefined = editor.storage.markdown.getMarkdown()
@@ -77,8 +93,17 @@ export function RichTextEditor({
emptyEditorClass: 'is-editor-empty',
}),
VideoEmbed,
+ Hashtag.configure({
+ tags,
+ suggestion: hashtagSuggestion,
+ }),
+ ItemMention.configure({
+ suggestion: itemMentionSuggestion,
+ items,
+ getItemColor,
+ }),
],
- content: preprocessVideoLinks(defaultValue),
+ content: preprocessMarkdown(defaultValue),
onUpdate: handleChange,
editorProps: {
attributes: {
@@ -89,7 +114,7 @@ export function RichTextEditor({
useEffect(() => {
if (editor.storage.markdown.getMarkdown() === '' || !editor.storage.markdown.getMarkdown()) {
- editor.commands.setContent(preprocessVideoLinks(defaultValue))
+ editor.commands.setContent(preprocessMarkdown(defaultValue))
}
}, [defaultValue, editor])
diff --git a/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx
index f7370e84..bfcd40db 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx
@@ -23,6 +23,7 @@ import { useNavigate } from 'react-router-dom'
import { useAppState } from '#components/AppShell/hooks/useAppState'
import { useDebounce } from '#components/Map/hooks/useDebounce'
import { useAddFilterTag } from '#components/Map/hooks/useFilter'
+import { useGetItemColor } from '#components/Map/hooks/useItemColor'
import { useItems } from '#components/Map/hooks/useItems'
import { useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
import { useTags } from '#components/Map/hooks/useTags'
@@ -48,6 +49,7 @@ export const SearchControl = () => {
const map = useMap()
const tags = useTags()
const items = useItems()
+ const getItemColor = useGetItemColor()
const leafletRefs = useLeafletRefs()
const addFilterTag = useAddFilterTag()
const appState = useAppState()
@@ -173,21 +175,7 @@ export const SearchControl = () => {
)}
{itemsResults.slice(0, 5).map((item) => {
- // Calculate color using the same logic as PopupView
- const itemTags =
- item.text
- ?.match(/#[^\s#]+/g)
- ?.map((tag) =>
- tags.find((t) => t.name.toLowerCase() === tag.slice(1).toLowerCase()),
- )
- .filter(Boolean) ?? []
-
- let color1 = item.layer?.markerDefaultColor ?? '#777'
- if (item.color) {
- color1 = item.color
- } else if (itemTags[0]) {
- color1 = itemTags[0].color
- }
+ const color1 = getItemColor(item, '#777')
return (
✅ Connected
}
- const tags = getItemTags(item)
return (