Skip to content

Commit 5541c17

Browse files
committed
fix(rich-editor): icon fallback for removed integrations; smoother divider nav
- A mention to a since-removed integration falls back to a generic icon so the chip is never icon-less (indistinguishable from prose) - Arrowing from a selected divider/image to an adjacent one selects it directly instead of stopping on the gap cursor between them, so stepping through a run of dividers is one press each
1 parent a7ada19 commit 5541c17

2 files changed

Lines changed: 35 additions & 8 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/keymap.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Editor } from '@tiptap/core'
22
import { Extension } from '@tiptap/core'
3+
import { NodeSelection } from '@tiptap/pm/state'
34
import { MENTION_PLUGIN_KEY } from './mention'
45
import { SLASH_COMMAND_PLUGIN_KEY } from './slash-command/slash-command'
56

@@ -36,6 +37,26 @@ function selectAdjacentLeaf(editor: Editor, direction: 'up' | 'down'): boolean {
3637
)
3738
}
3839

40+
/**
41+
* When a divider/image is already selected, arrowing toward an immediately-adjacent divider/image
42+
* selects that one directly instead of stopping on the gap cursor between them — so stepping through a
43+
* run of dividers is one press each. A non-leaf neighbour (a textblock) falls through to the default,
44+
* which moves the caret into it.
45+
*/
46+
function selectAdjacentSelectedLeaf(editor: Editor, direction: 'up' | 'down'): boolean {
47+
const { selection } = editor.state
48+
if (!(selection instanceof NodeSelection) || !SELECTABLE_LEAVES.has(selection.node.type.name)) {
49+
return false
50+
}
51+
const boundary = direction === 'up' ? selection.from : selection.to
52+
const resolved = editor.state.doc.resolve(boundary)
53+
const adjacent = direction === 'up' ? resolved.nodeBefore : resolved.nodeAfter
54+
if (!adjacent || !SELECTABLE_LEAVES.has(adjacent.type.name)) return false
55+
return editor.commands.setNodeSelection(
56+
direction === 'up' ? boundary - adjacent.nodeSize : boundary
57+
)
58+
}
59+
3960
/**
4061
* Editor-specific keyboard behavior layered on top of StarterKit's defaults:
4162
*
@@ -47,7 +68,9 @@ function selectAdjacentLeaf(editor: Editor, direction: 'up' | 'down'): boolean {
4768
* - **Mod-A** inside a code block selects only that block's contents; pressing it again (when the
4869
* block is already fully selected) falls through to the default whole-document select-all, the
4970
* same scoped behavior as a code editor.
50-
* - **ArrowUp/ArrowDown** select an adjacent divider or image (see {@link selectAdjacentLeaf}).
71+
* - **ArrowUp/ArrowDown** select an adjacent divider or image, whether arrowing off a textblock edge
72+
* ({@link selectAdjacentLeaf}) or stepping from one already-selected leaf to the next
73+
* ({@link selectAdjacentSelectedLeaf}).
5174
*/
5275
export const RichMarkdownKeymap = Extension.create({
5376
name: 'richMarkdownKeymap',
@@ -77,9 +100,12 @@ export const RichMarkdownKeymap = Extension.create({
77100
if (editor.state.selection.from === from && editor.state.selection.to === to) return false
78101
return editor.commands.setTextSelection({ from, to })
79102
},
80-
ArrowUp: ({ editor }) => !isSuggestionMenuOpen(editor) && selectAdjacentLeaf(editor, 'up'),
103+
ArrowUp: ({ editor }) =>
104+
!isSuggestionMenuOpen(editor) &&
105+
(selectAdjacentSelectedLeaf(editor, 'up') || selectAdjacentLeaf(editor, 'up')),
81106
ArrowDown: ({ editor }) =>
82-
!isSuggestionMenuOpen(editor) && selectAdjacentLeaf(editor, 'down'),
107+
!isSuggestionMenuOpen(editor) &&
108+
(selectAdjacentSelectedLeaf(editor, 'down') || selectAdjacentLeaf(editor, 'down')),
83109
}
84110
},
85111
})

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-icon.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ComponentType } from 'react'
2-
import { Database, File, Folder, Sparkles, Table, Workflow } from 'lucide-react'
2+
import { Box, Database, File, Folder, Sparkles, Table, Workflow } from 'lucide-react'
33
import { getBlock } from '@/blocks/registry'
44
import type { MentionKind } from './types'
55

@@ -17,10 +17,11 @@ const KIND_ICONS: Record<Exclude<MentionKind, 'integration'>, MentionIcon> = {
1717

1818
/**
1919
* Resolves the icon for a mention. Integrations use their brand icon from the block registry (keyed by
20-
* blockType, which is the mention `id`); every other kind uses a lucide category icon. Shared by the
21-
* menu rows and the inserted chip so both render the same icon.
20+
* blockType, which is the mention `id`), falling back to a generic icon if the block was since removed
21+
* so the chip is never icon-less; every other kind uses a lucide category icon. Shared by the menu
22+
* rows and the inserted chip so both render the same icon.
2223
*/
23-
export function mentionIcon(kind: MentionKind, id: string): MentionIcon | undefined {
24-
if (kind === 'integration') return getBlock(id)?.icon as MentionIcon | undefined
24+
export function mentionIcon(kind: MentionKind, id: string): MentionIcon {
25+
if (kind === 'integration') return (getBlock(id)?.icon as MentionIcon | undefined) ?? Box
2526
return KIND_ICONS[kind]
2627
}

0 commit comments

Comments
 (0)