mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-02-06 09:55:47 +00:00
fix(lint): resolve eslint errors in suggestion components
- Fix import order issues - Fix TypeScript type annotations - Use .at() for array access to satisfy lint rules - Cast props to correct types in command handlers - Fix template literal type issues Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c644f182e4
commit
0e9548e25f
@ -10,8 +10,13 @@ import { Markdown } from 'tiptap-markdown'
|
||||
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 {
|
||||
Hashtag,
|
||||
ItemMention,
|
||||
VideoEmbed,
|
||||
createHashtagSuggestion,
|
||||
createItemMentionSuggestion,
|
||||
} from '#components/TipTap/extensions'
|
||||
import { preprocessMarkdown } from '#components/TipTap/utils/preprocessMarkdown'
|
||||
|
||||
import { InputLabel } from './InputLabel'
|
||||
|
||||
@ -12,7 +12,7 @@ export const useGetItemColor = (): ((item: Item | undefined, fallback?: string)
|
||||
const getItemTags = useGetItemTags()
|
||||
|
||||
return useCallback(
|
||||
(item: Item | undefined, fallback: string = '#000') => {
|
||||
(item: Item | undefined, fallback = '#000') => {
|
||||
if (!item) return fallback
|
||||
|
||||
// 1. Item's own color takes highest priority
|
||||
@ -36,7 +36,7 @@ export const useGetItemColor = (): ((item: Item | undefined, fallback?: string)
|
||||
* Hook that returns the calculated color for a specific item.
|
||||
* Priority: item.color > first tag color > layer default color > fallback
|
||||
*/
|
||||
export const useItemColor = (item: Item | undefined, fallback: string = '#000'): string => {
|
||||
export const useItemColor = (item: Item | undefined, fallback = '#000'): string => {
|
||||
const getItemColor = useGetItemColor()
|
||||
return getItemColor(item, fallback)
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
/* eslint-disable camelcase */ // Directus database fields use snake_case
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { useAuth } from '#components/Auth/useAuth'
|
||||
import { useGetItemColor } from '#components/Map/hooks/useItemColor'
|
||||
import { useItems, useUpdateItem, useAddItem } from '#components/Map/hooks/useItems'
|
||||
import { useLayers } from '#components/Map/hooks/useLayers'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
import { useGetItemColor } from '#components/Map/hooks/useItemColor'
|
||||
import { useAddTag, useTags } from '#components/Map/hooks/useTags'
|
||||
import { MapOverlayPage } from '#components/Templates'
|
||||
|
||||
|
||||
@ -48,7 +48,9 @@ export function ActionButton({
|
||||
<>
|
||||
{hasUserPermission(collection, 'update', item) && (
|
||||
<>
|
||||
<div className={`tw:absolute tw:right-6 tw:bottom-4 tw:flex tw:flex-col ${customStyle}`}>
|
||||
<div
|
||||
className={`tw:absolute tw:right-6 tw:bottom-4 tw:flex tw:flex-col ${customStyle ?? ''}`}
|
||||
>
|
||||
{triggerItemSelected && (
|
||||
<button
|
||||
tabIndex={0}
|
||||
|
||||
@ -6,8 +6,8 @@ import { usePopupForm } from '#components/Map/hooks/usePopupForm'
|
||||
import { useSetSelectPosition } from '#components/Map/hooks/useSelectPosition'
|
||||
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
||||
import { StartEndView } from '#components/Map/Subcomponents/ItemPopupComponents'
|
||||
import { TextViewStatic } from '#components/Map/Subcomponents/ItemPopupComponents/TextViewStatic'
|
||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||
import { TextViewStatic } from '#components/Map/Subcomponents/ItemPopupComponents/TextViewStatic'
|
||||
|
||||
import { DateUserInfo } from './DateUserInfo'
|
||||
|
||||
|
||||
@ -8,8 +8,7 @@ import type { Tag } from '#types/Tag'
|
||||
import type { NodeViewProps } from '@tiptap/react'
|
||||
import type { SuggestionOptions } from '@tiptap/suggestion'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnySuggestionOptions = Partial<SuggestionOptions<any>>
|
||||
type AnySuggestionOptions = Partial<SuggestionOptions>
|
||||
|
||||
export interface HashtagOptions {
|
||||
tags: Tag[]
|
||||
@ -93,10 +92,7 @@ export const Hashtag = Node.create<HashtagOptions>({
|
||||
addStorage() {
|
||||
return {
|
||||
markdown: {
|
||||
serialize(
|
||||
state: { write: (text: string) => void },
|
||||
node: { attrs: { label: string } },
|
||||
) {
|
||||
serialize(state: { write: (text: string) => void }, node: { attrs: { label: string } }) {
|
||||
// Write as plain hashtag
|
||||
state.write(`#${node.attrs.label}`)
|
||||
},
|
||||
|
||||
@ -7,8 +7,7 @@ import type { Item } from '#types/Item'
|
||||
import type { NodeViewProps } from '@tiptap/react'
|
||||
import type { SuggestionOptions } from '@tiptap/suggestion'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnySuggestionOptions = Partial<SuggestionOptions<any>>
|
||||
type AnySuggestionOptions = Partial<SuggestionOptions>
|
||||
|
||||
export interface ItemMentionOptions {
|
||||
HTMLAttributes: Record<string, unknown>
|
||||
@ -137,7 +136,7 @@ function ItemMentionComponent({ node, editor, extension }: NodeViewProps) {
|
||||
const item = options.items?.find((i) => i.id === id)
|
||||
const color = options.getItemColor
|
||||
? options.getItemColor(item, 'var(--color-primary)')
|
||||
: item?.color ?? 'var(--color-primary)'
|
||||
: (item?.color ?? 'var(--color-primary)')
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
// Don't navigate when in edit mode
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { mergeAttributes, Node } from '@tiptap/core'
|
||||
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
|
||||
|
||||
import type { NodeViewProps } from '@tiptap/react'
|
||||
|
||||
@ -13,17 +13,17 @@ const RUMBLE_REGEX = /(?:https?:\/\/)?rumble\.com\/embed\/([a-zA-Z0-9_-]+)/
|
||||
* Extracts video provider and ID from a URL
|
||||
*/
|
||||
function parseVideoUrl(url: string): { provider: 'youtube' | 'rumble'; videoId: string } | null {
|
||||
let match = url.match(YOUTUBE_REGEX)
|
||||
let match = YOUTUBE_REGEX.exec(url)
|
||||
if (match) {
|
||||
return { provider: 'youtube', videoId: match[1] }
|
||||
}
|
||||
|
||||
match = url.match(YOUTUBE_SHORT_REGEX)
|
||||
match = YOUTUBE_SHORT_REGEX.exec(url)
|
||||
if (match) {
|
||||
return { provider: 'youtube', videoId: match[1] }
|
||||
}
|
||||
|
||||
match = url.match(RUMBLE_REGEX)
|
||||
match = RUMBLE_REGEX.exec(url)
|
||||
if (match) {
|
||||
return { provider: 'rumble', videoId: match[1] }
|
||||
}
|
||||
@ -57,7 +57,10 @@ export const VideoEmbed = Node.create<VideoEmbedOptions>({
|
||||
addStorage() {
|
||||
return {
|
||||
markdown: {
|
||||
serialize(state: { write: (text: string) => void }, node: { attrs: { provider: string; videoId: string } }) {
|
||||
serialize(
|
||||
state: { write: (text: string) => void },
|
||||
node: { attrs: { provider: string; videoId: string } },
|
||||
) {
|
||||
const { provider, videoId } = node.attrs
|
||||
const url =
|
||||
provider === 'youtube'
|
||||
|
||||
@ -2,10 +2,6 @@ export { Hashtag } from './Hashtag'
|
||||
export { ItemMention } from './ItemMention'
|
||||
export { VideoEmbed } from './VideoEmbed'
|
||||
|
||||
export {
|
||||
createHashtagSuggestion,
|
||||
createItemMentionSuggestion,
|
||||
SuggestionList,
|
||||
} from './suggestions'
|
||||
export { createHashtagSuggestion, createItemMentionSuggestion, SuggestionList } from './suggestions'
|
||||
|
||||
export type { SuggestionListRef } from './suggestions'
|
||||
|
||||
@ -4,10 +4,10 @@ import tippy from 'tippy.js'
|
||||
|
||||
import { SuggestionList } from './SuggestionList'
|
||||
|
||||
import type { Instance as TippyInstance } from 'tippy.js'
|
||||
import type { SuggestionOptions } from '@tiptap/suggestion'
|
||||
import type { Tag } from '#types/Tag'
|
||||
import type { SuggestionListRef } from './SuggestionList'
|
||||
import type { SuggestionOptions } from '@tiptap/suggestion'
|
||||
import type { Instance as TippyInstance } from 'tippy.js'
|
||||
|
||||
export const HashtagSuggestionPluginKey = new PluginKey('hashtagSuggestion')
|
||||
|
||||
@ -103,9 +103,9 @@ export function createHashtagSuggestion(
|
||||
}
|
||||
// Space key triggers selection of the first item (or creates new tag)
|
||||
if (props.event.key === ' ') {
|
||||
const firstItem = currentItems[0]
|
||||
if (firstItem && currentCommand) {
|
||||
currentCommand(firstItem)
|
||||
const firstItem = currentItems.at(0)
|
||||
if (firstItem) {
|
||||
currentCommand?.(firstItem)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -123,10 +123,10 @@ export function createHashtagSuggestion(
|
||||
let tagToInsert: Tag
|
||||
|
||||
// Create new tag if needed
|
||||
if ('isNew' in props && props.isNew) {
|
||||
if ('isNew' in props && (props as { isNew: boolean }).isNew) {
|
||||
const newTag: Tag = {
|
||||
id: crypto.randomUUID(),
|
||||
name: props.name,
|
||||
name: (props as { name: string }).name,
|
||||
color: '#888888', // Default color
|
||||
}
|
||||
if (addTag) {
|
||||
|
||||
@ -4,10 +4,10 @@ import tippy from 'tippy.js'
|
||||
|
||||
import { SuggestionList } from './SuggestionList'
|
||||
|
||||
import type { Instance as TippyInstance } from 'tippy.js'
|
||||
import type { SuggestionOptions } from '@tiptap/suggestion'
|
||||
import type { Item } from '#types/Item'
|
||||
import type { SuggestionListRef } from './SuggestionList'
|
||||
import type { SuggestionOptions } from '@tiptap/suggestion'
|
||||
import type { Instance as TippyInstance } from 'tippy.js'
|
||||
|
||||
export const ItemMentionSuggestionPluginKey = new PluginKey('itemMentionSuggestion')
|
||||
|
||||
@ -96,6 +96,7 @@ export function createItemMentionSuggestion(
|
||||
command: ({ editor, range, props }) => {
|
||||
// Insert item mention and a space after it
|
||||
// Using a single insertContent call with an array ensures atomic insertion
|
||||
const item = props as Item
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
@ -104,8 +105,8 @@ export function createItemMentionSuggestion(
|
||||
{
|
||||
type: 'itemMention',
|
||||
attrs: {
|
||||
id: props.id,
|
||||
label: props.name,
|
||||
id: item.id,
|
||||
label: item.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -35,7 +35,7 @@ export const SuggestionList = forwardRef<SuggestionListRef, SuggestionListProps>
|
||||
return true
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
const item = items[selectedIndex]
|
||||
const item = items.at(selectedIndex)
|
||||
if (item) command(item)
|
||||
return true
|
||||
}
|
||||
@ -67,7 +67,11 @@ export const SuggestionList = forwardRef<SuggestionListRef, SuggestionListProps>
|
||||
color = getItemColor(item as Item)
|
||||
}
|
||||
|
||||
const key = isNewTag ? `new-${item.name}` : 'id' in item ? item.id : `item-${index}`
|
||||
const key = isNewTag
|
||||
? `new-${item.name}`
|
||||
: 'id' in item
|
||||
? item.id
|
||||
: `item-${String(index)}`
|
||||
|
||||
return (
|
||||
<button
|
||||
@ -76,7 +80,9 @@ export const SuggestionList = forwardRef<SuggestionListRef, SuggestionListProps>
|
||||
index === selectedIndex ? 'tw:bg-base-200' : ''
|
||||
}`}
|
||||
style={color ? { color } : undefined}
|
||||
onClick={() => command(item)}
|
||||
onClick={() => {
|
||||
command(item)
|
||||
}}
|
||||
>
|
||||
{isNewTag ? (
|
||||
<span className='tw:flex tw:items-center tw:gap-1'>
|
||||
|
||||
@ -8,10 +8,7 @@ import type { JSONContent, Extensions } from '@tiptap/core'
|
||||
* Converts pre-processed markdown/HTML to TipTap JSON format.
|
||||
* Creates a temporary editor instance to parse the content.
|
||||
*/
|
||||
export function markdownToTiptapJson(
|
||||
content: string,
|
||||
extensions: Extensions,
|
||||
): JSONContent {
|
||||
export function markdownToTiptapJson(content: string, extensions: Extensions): JSONContent {
|
||||
// Create a temporary editor to parse HTML/markdown
|
||||
const editor = new Editor({
|
||||
extensions,
|
||||
|
||||
@ -13,10 +13,7 @@ export function simpleMarkdownToHtml(text: string, tags: Tag[]): string {
|
||||
let html = text
|
||||
|
||||
// Escape HTML first (but preserve our preprocessed tags)
|
||||
html = html
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
html = html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
|
||||
// Restore our preprocessed tags
|
||||
html = html
|
||||
@ -69,18 +66,15 @@ export function simpleMarkdownToHtml(text: string, tags: Tag[]): string {
|
||||
html = html.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||
|
||||
// Links: [text](url)
|
||||
html = html.replace(
|
||||
/\[([^\]]+)\]\(([^)]+)\)/g,
|
||||
(_, linkText: string, url: string) => {
|
||||
const isExternal = url.startsWith('http')
|
||||
const attrs = isExternal ? 'target="_blank" rel="noopener noreferrer"' : ''
|
||||
return `<a href="${url}" ${attrs}>${linkText}</a>`
|
||||
},
|
||||
)
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText: string, url: string) => {
|
||||
const isExternal = url.startsWith('http')
|
||||
const attrs = isExternal ? 'target="_blank" rel="noopener noreferrer"' : ''
|
||||
return `<a href="${url}" ${attrs}>${linkText}</a>`
|
||||
})
|
||||
|
||||
// Headers: # text, ## text, etc.
|
||||
html = html.replace(/^(#{1,6})\s+(.*)$/gm, (_, hashes: string, content: string) => {
|
||||
const level = hashes.length
|
||||
const level = String(hashes.length)
|
||||
return `<h${level}>${content}</h${level}>`
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user