seperate hashtags

This commit is contained in:
Anton Tranelis 2025-07-04 15:07:46 +02:00
parent 46705b2162
commit 28dd16dfeb
3 changed files with 71 additions and 16 deletions

40
lib/package-lock.json generated
View File

@ -57,9 +57,11 @@
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0", "rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-breaks": "^4.0.0", "remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0", "remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.10", "tiptap-markdown": "^0.8.10",
"unified": "^11.0.5", "unified": "^11.0.5",
@ -7244,6 +7246,29 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
"ccount": "^2.0.0",
"comma-separated-tokens": "^2.0.0",
"hast-util-whitespace": "^3.0.0",
"html-void-elements": "^3.0.0",
"mdast-util-to-hast": "^13.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"stringify-entities": "^4.0.0",
"zwitch": "^2.0.4"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-jsx-runtime": { "node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
@ -12009,6 +12034,21 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/rehype-stringify": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz",
"integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"hast-util-to-html": "^9.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-breaks": { "node_modules/remark-breaks": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",

View File

@ -145,9 +145,11 @@
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0", "rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-breaks": "^4.0.0", "remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0", "remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.10", "tiptap-markdown": "^0.8.10",
"unified": "^11.0.5", "unified": "^11.0.5",

View File

@ -6,12 +6,14 @@ import remarkBreaks from 'remark-breaks'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
import { useAddFilterTag } from '#components/Map/hooks/useFilter' import { useAddFilterTag } from '#components/Map/hooks/useFilter'
import { useTags } from '#components/Map/hooks/useTags' import { useGetItemTags, useTags } from '#components/Map/hooks/useTags'
import { decodeTag } from '#utils/FormatTags' import { decodeTag } from '#utils/FormatTags'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
export const TextPreview = ({ item }: { item: Item }) => { export const TextPreview = ({ item }: { item: Item }) => {
const getItemTags = useGetItemTags()
if (!item.text) return null if (!item.text) return null
// Text auf ~100 Zeichen stutzen (inkl. Ellipse „…“) // Text auf ~100 Zeichen stutzen (inkl. Ellipse „…“)
const previewRaw = truncate(item.text, { limit: 100, ellipsis: true }) as string const previewRaw = truncate(item.text, { limit: 100, ellipsis: true }) as string
@ -23,22 +25,21 @@ export const TextPreview = ({ item }: { item: Item }) => {
return ( return (
<div className='markdown'> <div className='markdown'>
<Markdown <Markdown remarkPlugins={[remarkBreaks, remarkGfm]} rehypePlugins={[rehypeRaw]}>
remarkPlugins={[remarkBreaks, remarkGfm]} {removeMentionSpans(removeHashtags(withExtraHashes))}
rehypePlugins={[rehypeRaw]}
components={{ span: Span }}
>
{withExtraHashes}
</Markdown> </Markdown>
{getItemTags(item).map((tag) => (
<HashTag tag={tag} key={tag} />
))}
</div> </div>
) )
} }
export const HashTag = ({ tag }: { tag: string }) => { export const HashTag = ({ tag }: { tag: Tag }) => {
const tags = useTags() const tags = useTags()
const t = tags.find((t) => t.name === tag.slice(1)) const t = tags.find((t) => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())
const addFilterTag = useAddFilterTag() const addFilterTag = useAddFilterTag()
if (!t) return <span>{tag}</span> if (!t) return null
return ( return (
<a <a
className='hashtag' className='hashtag'
@ -49,14 +50,26 @@ export const HashTag = ({ tag }: { tag: string }) => {
addFilterTag(t) addFilterTag(t)
}} }}
> >
{decodeTag(tag)} {`#${decodeTag(tag.name)} `}
</a> </a>
) )
} }
export const Span = (node) => { function removeMentionSpans(html) {
if (node['data-type'] === 'mention') { return html.replace(
return <HashTag tag={node.children} /> /<span\b(?=[^>]*\bdata-type="mention")(?=[^>]*\bclass="mention")[^>]*>[\s\S]*?<\/span>/gi,
} '',
return <span {...node}>{node.children}</span> )
} }
function removeHashtags(str) {
return str
// 1. Hashtags entfernen, außer sie stehen am Zeilenanfang als Markdown-Heading
.replace(
/(^|\s)(?!#{1,6}\s)(#[A-Za-z0-9_]+)\b/g,
'$1'
)
// 3. Anfangs/Ende trimmen
.trim()
}