Skip to content

Commit fd630a3

Browse files
New Combobox with Preview on Extension (#80)
#80 * added editor preview renderer to extension dibba; similar plugin generation on extension; Signed-off-by: Sahil Shubham <[email protected]> * snippet preview in dibba; moved styles to shared; Signed-off-by: Sahil Shubham <[email protected]> * fixed import; Signed-off-by: Sahil Shubham <[email protected]> * moved preview meta to shared; Signed-off-by: Sahil Shubham <[email protected]> * removed unnecessary stuff from extension's mex-editor; Signed-off-by: Sahil Shubham <[email protected]> * added preview to extension combobox; and prettier being prettier; Signed-off-by: Sahil Shubham <[email protected]> * conditional react minification for better debugging; Signed-off-by: Sahil Shubham <[email protected]> * removed log; added TODO; Signed-off-by: Sahil Shubham <[email protected]> * fixed dibba snippet insertion; added todo for link captures; Signed-off-by: Sahil Shubham <[email protected]>
1 parent 061c63a commit fd630a3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+866
-733
lines changed

apps/extension/src/Components/Dibba/index.tsx

+64-22
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
import { LinkCapture, parseSnippet, Snippet } from '@mexit/core'
1+
import { LinkCapture, parseSnippet, QuickLinkType, Snippet } from '@mexit/core'
22
import React, { useEffect, useRef, useState } from 'react'
33
import { Icon } from '@iconify/react'
44
import fuzzysort from 'fuzzysort'
55

66
import { useSnippets } from '../../Hooks/useSnippets'
77
import { useSputlitContext, VisualState } from '../../Hooks/useSputlitContext'
8-
import { ComboboxItem, ComboboxRoot, Img, ItemCenterWrapper, ItemDesc, ItemTitle } from './styled'
8+
import { ComboboxItem, ComboboxRoot, ItemCenterWrapper, ItemDesc, ItemRightIcons, ItemTitle } from './styled'
99
import { useShortenerStore } from '../../Hooks/useShortener'
1010
import { getDibbaText } from '../../Utils/getDibbaText'
11+
import { ComboboxShortcuts, ComboSeperator, DisplayShortcut, ShortcutText } from '@mexit/shared'
12+
import { ElementTypeBasedShortcut } from '../../Editor/components/ComboBox'
13+
import EditorPreviewRenderer from '../EditorPreviewRenderer'
14+
import usePointerMovedSinceMount from '../../Hooks/usePointerMovedSinceMount'
1115

1216
// This functions provides the 'to be' range and text content
1317
// Needed because keydown event happens before there is a selection or content change
@@ -40,16 +44,21 @@ export default function Dibba() {
4044

4145
const linkCaptures = useShortenerStore((store) => store.linkCaptures)
4246
const snippets = useSnippets().getSnippets()
47+
const pointerMoved = usePointerMovedSinceMount()
4348

4449
const data = [
50+
// TODO: fix link captures after discussion
4551
...linkCaptures.map((item) => ({
4652
id: item.shortenedURL,
4753
title: item.short,
48-
// TODO: find a way to use favicons but single array of results
4954
icon: 'ri:link',
5055
content: item.shortenedURL
5156
})),
52-
...snippets
57+
...snippets.map((item) => ({
58+
type: QuickLinkType.snippet,
59+
icon: item?.icon || 'ri:quill-pen-line',
60+
...item
61+
}))
5362
]
5463

5564
const insertSnippet = (item: Snippet) => {
@@ -86,7 +95,7 @@ export default function Dibba() {
8695
console.log(error)
8796
}
8897

89-
if (item.icon === 'ri:quill-pen-line') {
98+
if (item.type === QuickLinkType.snippet) {
9099
insertSnippet(item as Snippet)
91100
} else if (item.icon === 'ri:link') {
92101
// TODO: transform again to type linkCapture
@@ -174,32 +183,65 @@ export default function Dibba() {
174183
setOffsetTop(window.innerHeight < top + dibbaRef.current.clientHeight)
175184
})
176185

186+
const listItem = results[activeIndex]
187+
const itemShortcut = listItem?.type ? ElementTypeBasedShortcut[listItem?.type] : undefined
188+
177189
return (
178190
<ComboboxRoot
179191
id="dibba-container"
180192
ref={dibbaRef}
181193
top={top}
182194
left={left}
183195
offsetTop={offsetTop}
184-
offsetRight={window.innerWidth < left + 225}
196+
offsetRight={window.innerWidth < left + 500}
185197
isOpen={dibbaState.visualState === VisualState.showing}
186198
>
187-
{results.map((item, index) => {
188-
return (
189-
<ComboboxItem
190-
key={index}
191-
highlighted={index === activeIndex}
192-
onMouseDown={() => {
193-
handleClick(item)
194-
}}
195-
>
196-
<Icon height={18} key={item.id} icon={item.icon} />
197-
<ItemCenterWrapper>
198-
<ItemTitle>{item.title}</ItemTitle>
199-
</ItemCenterWrapper>
200-
</ComboboxItem>
201-
)
202-
})}
199+
<div style={{ flex: 1 }}>
200+
{results.map((item, index) => {
201+
return (
202+
<ComboboxItem
203+
key={index}
204+
highlighted={index === activeIndex}
205+
onMouseDown={() => {
206+
handleClick(item)
207+
}}
208+
onPointerMove={() => pointerMoved && setActiveIndex(index)}
209+
>
210+
<Icon height={18} key={item.id} icon={item.icon} />
211+
<ItemCenterWrapper>
212+
<ItemTitle>{item.title}</ItemTitle>
213+
{item.desc && <ItemDesc>{item.desc}</ItemDesc>}
214+
</ItemCenterWrapper>
215+
{item.rightIcons && (
216+
<ItemRightIcons>
217+
{item.rightIcons.map((i: string) => (
218+
<Icon key={item.key + i} icon={i} />
219+
))}
220+
</ItemRightIcons>
221+
)}
222+
</ComboboxItem>
223+
)
224+
})}
225+
{itemShortcut && (
226+
<ComboboxShortcuts>
227+
{Object.entries(itemShortcut).map(([key, shortcut]) => {
228+
return (
229+
<ShortcutText key={key}>
230+
<DisplayShortcut shortcut={shortcut.keystrokes} /> <div className="text">{shortcut.title}</div>
231+
</ShortcutText>
232+
)
233+
})}
234+
</ComboboxShortcuts>
235+
)}
236+
</div>
237+
238+
{listItem?.content && (
239+
<ComboSeperator>
240+
<section>
241+
<EditorPreviewRenderer noMouseEvents content={listItem.content} editorId={listItem.id} />
242+
</section>
243+
</ComboSeperator>
244+
)}
203245
</ComboboxRoot>
204246
)
205247
}

apps/extension/src/Components/Dibba/styled.tsx

+19-9
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,35 @@ export const ComboboxRoot = styled.ul<{
1010
${({ isOpen, theme, top, left, offsetTop, offsetRight }) =>
1111
isOpen &&
1212
css`
13+
display: flex;
1314
top: calc(${top}px + 1em);
1415
left: ${left}px;
1516
position: absolute;
1617
padding: 0;
1718
margin: 0;
1819
z-index: 9999999;
19-
background: ${theme.colors.background.modal};
20-
width: 225px;
20+
/* background: ${theme.colors.background.modal}; */
21+
/* width: 225px; */
2122
overflow: hidden;
2223
border-radius: 8px;
23-
box-shadow: rgba(0, 0, 0, 0.133) 0 3.2px 7.2px 0, rgba(0, 0, 0, 0.11) 0 0.6px 1.8px 0;
24+
/* box-shadow: rgba(0, 0, 0, 0.133) 0 3.2px 7.2px 0, rgba(0, 0, 0, 0.11) 0 0.6px 1.8px 0; */
2425
2526
transform: ${offsetTop ? css`translateY(calc(-100% - 1em))` : ''} ${offsetRight ? css`translateX(-100%)` : ''};
26-
`}
27-
`
2827
29-
export const Img = styled.img`
30-
width: 18px;
31-
aspect-ratio: 1/1;
28+
> div {
29+
background: ${theme.colors.background.modal};
30+
height: fit-content;
31+
/* max-height: 400px; */
32+
box-shadow: rgba(0, 0, 0, 0.133) 0 3.2px 7.2px 0, rgba(0, 0, 0, 0.11) 0 0.6px 1.8px 0;
33+
border-radius: ${theme.borderRadius.small};
34+
35+
> section {
36+
max-height: 30vh;
37+
overflow-y: auto;
38+
overflow-x: hidden;
39+
}
40+
}
41+
`}
3242
`
3343

3444
export const ItemTitle = styled.div``
@@ -40,7 +50,7 @@ export const ItemRightIcons = styled.div`
4050
export const ItemDesc = styled.div`
4151
margin-top: ${({ theme }) => theme.spacing.tiny};
4252
color: ${({ theme }) => theme.colors.text.fade};
43-
font-size: 0.8em;
53+
font-size: 0.8rem;
4454
white-space: nowrap;
4555
overflow: hidden;
4656
text-overflow: ellipsis;

apps/extension/src/Components/Editor/Components.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import {
1414
ELEMENT_TAG
1515
} from '@mexit/core'
1616

17-
export const components = {
17+
export const editorPreviewComponents = {
1818
[ELEMENT_LINK]: withProps(LinkElement, {
1919
as: 'a'
2020
}),
2121
[ELEMENT_PARAGRAPH]: withProps(StyledElement, {
2222
styles: {
2323
root: {
24-
margin: '0.1em 0 0'
24+
margin: '0.1rem 0 0'
2525
}
2626
}
2727
}),
@@ -31,8 +31,8 @@ export const components = {
3131
[ELEMENT_TABLE]: TableWrapper
3232
}
3333

34-
// const components = createPlateUI({
35-
// ...editorPreviewComponents
36-
// })
34+
const components = createPlateUI({
35+
...editorPreviewComponents
36+
})
3737

3838
export default components

apps/extension/src/Components/Editor/index.tsx

+11-10
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ import { useDebouncedCallback } from 'use-debounce'
55
import React, { useState, useMemo } from 'react'
66

77
import { EditorStyles, useEditorChange } from '@mexit/shared'
8-
import generatePlugins from '../../Utils/plugins'
98
import { useAuthStore } from '../../Hooks/useAuth'
109
import { EditorWrapper } from './styled'
1110
import { useSputlitContext } from '../../Hooks/useSputlitContext'
12-
import { useTagStore } from '../../Hooks/useTags'
1311

1412
import components from './Components'
1513
import BallonMarkToolbarButtons from './BalloonToolbar/EditorBalloonToolbar'
@@ -46,10 +44,8 @@ const commands = [
4644
export const Editor: React.FC<EditorProps> = ({ readOnly, onChange }) => {
4745
const { searchResults, activeIndex, activeItem } = useSputlitContext()
4846
const { previewMode, nodeContent, node, setPreviewMode } = useEditorContext()
49-
const ilinks = useDataStore((store) => store.ilinks)
5047

51-
const addTags = useTagStore((store) => store.addTags)
52-
const tags = useTagStore((store) => store.tags)
48+
const { tags, addTag, ilinks, addILink } = useDataStore()
5349

5450
useEditorChange(node.nodeid, nodeContent, onChange)
5551

@@ -58,11 +54,11 @@ export const Editor: React.FC<EditorProps> = ({ readOnly, onChange }) => {
5854
keys: {
5955
tag: {
6056
slateElementType: ELEMENT_TAG,
61-
newItemHandler: (tag: string) => addTags({ id: 'TAG_1234', text: tag })
57+
newItemHandler: (newItem) => addTag(newItem)
6258
},
6359
ilink: {
6460
slateElementType: ELEMENT_ILINK,
65-
newItemHandler: (ilink: string, parentId?: string) => console.log(`ilink: ${ilink} | ParentID: ${parentId}`)
61+
newItemHandler: (newItem, parentId?) => addILink({ ilink: newItem, parentId })
6662
},
6763
slash_command: {
6864
slateElementType: 'slash_command',
@@ -88,13 +84,18 @@ export const Editor: React.FC<EditorProps> = ({ readOnly, onChange }) => {
8884
cbKey: ComboboxKey.TAG,
8985
trigger: '#',
9086
data: tags.map((t) => ({ ...t, text: t.text })),
91-
icon: 'add-something-here'
87+
icon: 'ri:hashtag'
9288
},
9389
ilink: {
9490
cbKey: ComboboxKey.ILINK,
9591
trigger: '[[',
96-
data: ilinks.map((l) => ({ ...l, value: l.path, text: l.path })),
97-
icon: 'add-something-here'
92+
data: ilinks.map((l) => ({
93+
...l,
94+
value: l.nodeid,
95+
text: l.path,
96+
type: QuickLinkType.backlink
97+
})),
98+
icon: 'ri:file-list-2-line'
9899
},
99100
slash_command: {
100101
cbKey: ComboboxKey.SLASH_COMMAND,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React, { useEffect, useMemo } from 'react'
2+
import styled from 'styled-components'
3+
4+
import { EditorStyles, FadeContainer, TodoContainer } from '@mexit/shared'
5+
6+
import { useEditorChange } from '@mexit/shared'
7+
8+
import { Plate, PlatePlugin } from '@udecode/plate'
9+
import components from './Editor/Components'
10+
import useMemoizedPlugins from '../Editor/plugins'
11+
12+
interface EditorPreviewRendererProps {
13+
content: any[] // eslint-disable-line @typescript-eslint/no-explicit-any
14+
editorId: string
15+
noStyle?: boolean
16+
/**
17+
* Block that will be focused on render
18+
*/
19+
blockId?: string
20+
noMouseEvents?: boolean
21+
onDoubleClick?: (ev: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
22+
plugins?: PlatePlugin[]
23+
}
24+
25+
const PreviewStyles = styled(EditorStyles)<{ noMouseEvents: boolean }>`
26+
${({ noMouseEvents }) => noMouseEvents && 'pointer-events: none;'};
27+
/* user-select: none; */
28+
font-size: 0.9rem;
29+
30+
${TodoContainer}, button, input, textarea, select, option {
31+
pointer-events: none;
32+
}
33+
`
34+
35+
const EditorPreviewRenderer = ({
36+
content,
37+
editorId,
38+
blockId,
39+
noStyle,
40+
noMouseEvents,
41+
onDoubleClick
42+
}: EditorPreviewRendererProps) => {
43+
const editableProps = {
44+
placeholder: 'Murmuring the mex hype... ',
45+
spellCheck: false,
46+
style: noStyle
47+
? {}
48+
: {
49+
padding: '15px'
50+
},
51+
readOnly: true
52+
}
53+
54+
// We get memoized plugins
55+
const plugins = useMemoizedPlugins(components, { exclude: { dnd: true } })
56+
// const setHighlights = useBlockHighlightStore((s) => s.setHighlightedBlockIds)
57+
// const { focusBlock } = useFocusBlock()
58+
59+
useEffect(() => {
60+
const timeoutId = setTimeout(() => {
61+
if (blockId) {
62+
// mog('editorPreviewRenderer', { blockId, editorId })
63+
// focusBlock(blockId, editorId)
64+
// setHighlights([blockId], 'preview')
65+
}
66+
}, 300)
67+
68+
return () => {
69+
clearTimeout(timeoutId)
70+
}
71+
}, [blockId, editorId, content])
72+
73+
useEditorChange(editorId, content)
74+
75+
return (
76+
<PreviewStyles
77+
noMouseEvents={noMouseEvents}
78+
onClick={(ev) => {
79+
if (onDoubleClick && ev.detail === 2) {
80+
onDoubleClick(ev)
81+
}
82+
}}
83+
>
84+
<FadeContainer fade={blockId !== undefined}>
85+
<Plate id={editorId} editableProps={editableProps} value={content} plugins={plugins} />
86+
</FadeContainer>
87+
</PreviewStyles>
88+
)
89+
}
90+
export default EditorPreviewRenderer

apps/extension/src/Editor/components/ComboBox/config.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const useComboboxConfig = (
5858
...(config.onChangeConfig as any)
5959
}
6060

61-
const prePlugins = useMemoizedPlugins(customPlugins ?? generatePlugins(), components)
61+
const prePlugins = useMemoizedPlugins(components)
6262

6363
const plugins = [
6464
...prePlugins,

0 commit comments

Comments
 (0)