Skip to content

Commit 91f4e4d

Browse files
authored
[MexIt] Snippets, Tags and Search fixes (#99)
#99 * Unique note path, short block Ids added * Multiple save Snippet API calls fixed * Empty notes content, search-snippet broken ui fixed * Auto save, snippet & editor unmount save added * Combobox create tags, list all tags fixed * base node load fixed, Update info from content on load
1 parent 4c47413 commit 91f4e4d

33 files changed

+248
-94
lines changed

apps/webapp/src/Actions/Components/Tags.tsx

+5-6
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,12 @@ export const Tags: React.FC<TagsProps> = ({ userTags, addNewTag, removeTag }: Ta
4747
const { key } = e
4848
const trimmedInput = input.trim()
4949
const tagTexts = new Array<string>()
50-
userTags.forEach((tag) => tagTexts.push(tag.text))
50+
userTags.forEach((tag) => tagTexts.push(tag.value))
5151

5252
if (key === 'Enter' && trimmedInput.length && !tagTexts.includes(trimmedInput)) {
5353
e.preventDefault()
5454
const t: Tag = {
55-
id: `TAG_${nanoid()}`,
56-
text: trimmedInput
55+
value: trimmedInput
5756
}
5857
addNewTag(t)
5958
setInput('')
@@ -64,7 +63,7 @@ export const Tags: React.FC<TagsProps> = ({ userTags, addNewTag, removeTag }: Ta
6463
const poppedTag = tagsCopy.pop()
6564
e.preventDefault()
6665
removeTag(poppedTag)
67-
setInput(poppedTag.text)
66+
setInput(poppedTag.value)
6867
}
6968

7069
setIsKeyReleased(false)
@@ -89,8 +88,8 @@ export const Tags: React.FC<TagsProps> = ({ userTags, addNewTag, removeTag }: Ta
8988
{/* TODO: recommend and show recent used tags */}
9089
<TagsContainer>
9190
{userTags.map((tag) => (
92-
<Tagg key={tag.id} className="tag">
93-
{tag.text}
91+
<Tagg key={tag.value} className="tag">
92+
{tag.value}
9493
<button onClick={() => removeTag(tag)}>x</button>
9594
</Tagg>
9695
))}

apps/webapp/src/Components/Editor/ContentEditor.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const ContentEditor = () => {
3434
const isBlockMode = useBlockStore((store) => store.isBlockMode)
3535
const { setShowLoader } = useLayoutStore()
3636

37-
const { addOrUpdateValBuffer, getBufferVal } = useEditorBuffer()
37+
const { addOrUpdateValBuffer, saveAndClearBuffer, getBufferVal } = useEditorBuffer()
3838
const { node, fsContent } = useEditorStore(
3939
(state) => ({ nodeid: state.node.nodeid, node: state.node, fsContent: state.content }),
4040
shallow
@@ -95,6 +95,10 @@ const ContentEditor = () => {
9595
}
9696
}, [])
9797

98+
useEffect(() => {
99+
return () => saveAndClearBuffer()
100+
}, [])
101+
98102
return (
99103
<StyledEditor showGraph={false} className="mex_editor">
100104
<Toolbar />
@@ -107,7 +111,10 @@ const ContentEditor = () => {
107111
readOnly={readOnly}
108112
nodeUID={nodeId}
109113
nodePath={node.path}
110-
content={fsContent?.content ?? defaultContent.content}
114+
onAutoSave={(val) => {
115+
saveAndClearBuffer()
116+
}}
117+
content={fsContent?.content?.length ? fsContent?.content : defaultContent.content}
111118
onChange={onChangeSave}
112119
/>
113120
</EditorWrapper>

apps/webapp/src/Components/Editor/Editor.tsx

+21-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { LinkElement, MediaEmbedElement, TableWrapper } from '@mexit/shared'
77

88
import TagWrapper from './TagWrapper'
99
import BallonMarkToolbarButtons from './BalloonToolbar/EditorBalloonToolbar'
10-
import { ELEMENT_ILINK, ELEMENT_INLINE_BLOCK, ELEMENT_TAG, ELEMENT_TODO_LI } from '@mexit/core'
10+
import { ELEMENT_ILINK, ELEMENT_INLINE_BLOCK, ELEMENT_TAG, ELEMENT_TODO_LI, NodeEditorContent } from '@mexit/core'
1111
import Todo from '../Todo'
1212
import { useEditorChange } from '@mexit/shared'
1313
import { EditorStyles } from '@mexit/shared'
@@ -38,9 +38,10 @@ interface EditorProps {
3838
readOnly?: boolean
3939
onChange?: any // eslint-disable-line @typescript-eslint/no-explicit-any
4040
autoFocus?: boolean
41+
onAutoSave?: (content: NodeEditorContent) => void
4142
}
4243

43-
const Editor: React.FC<EditorProps> = ({ nodeUID, nodePath, content, readOnly, onChange, autoFocus }) => {
44+
const Editor: React.FC<EditorProps> = ({ nodeUID, nodePath, content, readOnly, onChange, autoFocus, onAutoSave }) => {
4445
const tags = useDataStore((store) => store.tags)
4546
const addTag = useDataStore((store) => store.addTag)
4647
const ilinks = useDataStore((store) => store.ilinks)
@@ -152,7 +153,7 @@ const Editor: React.FC<EditorProps> = ({ nodeUID, nodePath, content, readOnly, o
152153
tag: {
153154
cbKey: ComboboxKey.TAG,
154155
trigger: '#',
155-
data: tags.map((t) => ({ ...t, value: t.text })),
156+
data: tags.map((t) => ({ ...t, text: t.value })),
156157
icon: 'ri:hashtag'
157158
},
158159
slash_command: {
@@ -179,10 +180,24 @@ const Editor: React.FC<EditorProps> = ({ nodeUID, nodePath, content, readOnly, o
179180
withBalloonToolbar: true
180181
}
181182

182-
const debounced = useDebouncedCallback((value) => {
183+
const onDelayPerform = useDebouncedCallback((value) => {
183184
const f = !readOnly && typeof onChange === 'function' ? onChange : () => undefined
184185
f(value)
185-
}, 1000)
186+
}, 400)
187+
188+
const saveAfterDelay = useDebouncedCallback(
189+
typeof onAutoSave === 'function' ? onAutoSave : () => undefined,
190+
30 * 1000 // After 30 seconds
191+
)
192+
193+
const onChangeContent = (val: NodeEditorContent) => {
194+
onDelayPerform(val)
195+
196+
if (onAutoSave) {
197+
saveAfterDelay.cancel()
198+
saveAfterDelay(val)
199+
}
200+
}
186201

187202
const comboboxConfig: ComboboxConfig = {
188203
onChangeConfig: comboOnChangeConfig,
@@ -199,7 +214,7 @@ const Editor: React.FC<EditorProps> = ({ nodeUID, nodePath, content, readOnly, o
199214
}}
200215
BalloonMarkToolbarButtons={<BallonMarkToolbarButtons />}
201216
// debug
202-
onChange={debounced}
217+
onChange={onChangeContent}
203218
options={editorOptions}
204219
editorId={nodeUID}
205220
value={content}

apps/webapp/src/Components/Editor/SnippetEditor.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import tinykeys from 'tinykeys'
1313
import { useSnippetBuffer, useSnippetBufferStore } from '../../Hooks/useEditorBuffer'
1414
import { useRouting, ROUTE_PATHS, NavigationType } from '../../Hooks/useRouting'
1515
import { SnippetSaverButton } from '../Saver'
16-
import { useApi } from '../../Hooks/useApi'
1716
import { useSnippetStore } from '../../Stores/useSnippetStore'
1817

1918
type Inputs = {
@@ -24,7 +23,6 @@ const SnippetEditor = () => {
2423
const snippet = useSnippetStore((store) => store.editor.snippet)
2524
const { goTo } = useRouting()
2625

27-
const api = useApi()
2826
const {
2927
register,
3028
formState: { errors }
@@ -69,7 +67,6 @@ const SnippetEditor = () => {
6967
mog('onChangeSave', { val })
7068
if (val) {
7169
addOrUpdateValBuffer(snippet.id, val)
72-
api.saveSnippetAPI(snippet.id, snippet.title, val)
7370
}
7471
}
7572

@@ -104,6 +101,7 @@ const SnippetEditor = () => {
104101
})
105102

106103
return () => {
104+
saveSnippet()
107105
unsubscribe()
108106
}
109107
}, [])
@@ -115,12 +113,13 @@ const SnippetEditor = () => {
115113
// const snippet = useSnippetStore.getState().sn
116114
}
117115

118-
const returnToSnippets = () => {
116+
const saveSnippet = () => {
119117
saveAndClearBuffer()
120118
// updater()
121-
goTo(ROUTE_PATHS.snippets, NavigationType.push)
122119
}
123120

121+
const returnToSnippets = () => goTo(ROUTE_PATHS.snippets, NavigationType.push)
122+
124123
const defaultValue = snippet && snippet.title !== DRAFT_NODE ? snippet.title : ''
125124

126125
const onDelay = debounce((value) => onChangeTitle(value), 250)
@@ -161,6 +160,7 @@ const SnippetEditor = () => {
161160
/> */}
162161
<SnippetSaverButton
163162
getSnippetExtras={getSnippetExtras}
163+
noButton
164164
callbackAfterSave={callbackAfterSave}
165165
title="Save Snippet"
166166
/>

apps/webapp/src/Components/Editor/Toolbar.tsx

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react'
22
import focusLine from '@iconify/icons-ri/focus-line'
33
import { useSingleton } from '@tippyjs/react'
4+
import shareLine from '@iconify/icons-ri/share-line'
45

56
import { Loading, ToolbarTooltip, IconButton } from '@mexit/shared'
67

@@ -19,18 +20,28 @@ const Toolbar = () => {
1920
const nodeid = useEditorStore((state) => state.node.nodeid)
2021
const [source, target] = useSingleton()
2122
const shortcuts = useHelpStore((store) => store.shortcuts)
23+
const showShareOptions = useLayoutStore((store) => store.showShareOptions)
24+
const toggleShareOptions = useLayoutStore((store) => store.toggleShareOptions)
2225

2326
return (
2427
<NodeInfo {...getFocusProps(focusMode)}>
2528
<NodeRenameOnlyTitle />
2629
{fetchingContent && <Loading dots={3} />}
2730
<InfoTools>
2831
<ToolbarTooltip singleton={source} />
29-
<ToolbarTooltip singleton={target} content="Bookmark">
32+
<IconButton
33+
singleton={target}
34+
size={24}
35+
icon={shareLine}
36+
title="Share"
37+
highlight={showShareOptions}
38+
onClick={toggleShareOptions}
39+
/>
40+
{/* <ToolbarTooltip singleton={target} content="Bookmark">
3041
<span tabIndex={0}>
3142
<BookmarkButton nodeid={nodeid} />
3243
</span>
33-
</ToolbarTooltip>
44+
</ToolbarTooltip> */}
3445
<IconButton
3546
singleton={target}
3647
size={24}

apps/webapp/src/Components/EditorInfobar/index.tsx

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import React from 'react'
2-
import Metadata from './Metadata'
2+
import { animated, useSpring } from 'react-spring'
3+
import styled from 'styled-components'
4+
import { useLayoutStore } from '../../Stores/useLayoutStore'
35

46
import ShareOptions from './ShareOptions'
57

8+
const StyledEditorInfo = styled(animated.div)``
9+
610
const EditorInfoBar = () => {
11+
const showShareOptions = useLayoutStore((store) => store.showShareOptions)
12+
13+
const transition = useSpring({
14+
opacity: showShareOptions ? 1 : 0,
15+
height: showShareOptions ? '4rem' : '0rem',
16+
y: showShareOptions ? 0 : 5
17+
})
18+
719
return (
8-
<>
20+
<StyledEditorInfo style={transition}>
921
<ShareOptions />
10-
</>
22+
</StyledEditorInfo>
1123
)
1224
}
1325

apps/webapp/src/Components/Saver.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,12 @@ interface SnippetSaverButtonProps extends SaverButtonProps {
203203
getSnippetExtras: () => SnippetExtras
204204
}
205205

206-
export const SnippetSaverButton = ({ callbackAfterSave, title, getSnippetExtras }: SnippetSaverButtonProps) => {
206+
export const SnippetSaverButton = ({
207+
callbackAfterSave,
208+
title,
209+
getSnippetExtras,
210+
noButton
211+
}: SnippetSaverButtonProps) => {
207212
const { onSave: onSaveFs } = useSnippetSaver()
208213
const shortcuts = useHelpStore((state) => state.shortcuts)
209214
const { trackEvent } = useAnalytics()
@@ -229,6 +234,8 @@ export const SnippetSaverButton = ({ callbackAfterSave, title, getSnippetExtras
229234
}
230235
})
231236

237+
if (noButton) return <></>
238+
232239
return (
233240
<IconButton
234241
size={24}

apps/webapp/src/Data/links.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import searchLine from '@iconify/icons-ri/search-line'
1010
import { ROUTE_PATHS } from '../Hooks/useRouting'
1111
import { useHelpStore } from '../Stores/useHelpStore'
1212
import { NavLinkData } from '../Types/Nav'
13-
import { useLinks } from '../Hooks/useLinks'
13+
import { getNodeidFromPathAndLinks, useLinks } from '../Hooks/useLinks'
1414
import { useDataStore } from '../Stores/useDataStore'
1515
import { useEditorStore } from '../Stores/useEditorStore'
1616
import { useReminderStore } from '../Stores/useReminderStore'
@@ -27,12 +27,19 @@ export const GetIcon = (icon: any): React.ReactNode => <Icon icon={icon} />
2727
const useNavlinks = () => {
2828
const shortcuts = useHelpStore((store) => store.shortcuts)
2929
const nodeid = useEditorStore((store) => store.node.nodeid)
30+
const baseNodeId = useDataStore((store) => store.baseNodeId)
31+
3032
const reminders = useReminderStore((store) => store.reminders)
3133
const ilinks = useDataStore((store) => store.ilinks)
3234
const archive = useDataStore((store) => store.archive)
3335
const tasks = useTodoStore((store) => store.todos)
3436
const { getLinkCount } = useLinks()
3537

38+
const noteId = useMemo(() => {
39+
const currentNotId = nodeid === '__null__' ? getNodeidFromPathAndLinks(ilinks, baseNodeId) : nodeid
40+
return currentNotId
41+
}, [nodeid, ilinks])
42+
3643
const count = useMemo(() => getLinkCount(), [reminders, ilinks, archive, tasks])
3744

3845
const getLinks = () => {
@@ -51,7 +58,7 @@ const useNavlinks = () => {
5158
// },
5259
{
5360
title: 'Notes',
54-
path: `${ROUTE_PATHS.node}/${nodeid}`,
61+
path: `${ROUTE_PATHS.node}/${noteId}`,
5562
shortcut: shortcuts.showEditor.keystrokes,
5663
icon: GetIcon(fileDocument),
5764
count: count.notes

apps/webapp/src/Editor/Actions/withDraggables.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { RelativeTime } from '@mexit/shared'
3030
import useBlockStore from '../../Stores/useBlockStore'
3131
import { ProfileImage } from '../../Components/User/ProfileImage'
3232
import { useEditorStore } from '../../Stores/useEditorStore'
33+
import { IS_DEV } from '@mexit/core'
3334

3435
const StyledTip = styled.div`
3536
display: flex;
@@ -167,11 +168,13 @@ export const DraggerContent = ({ element }: any) => {
167168
<>
168169
<div>Drag to move</div>
169170
<div>Click to select</div>
170-
<div>
171-
<span>
172-
<i>{element && element.id}</i> - {element && element.type}
173-
</span>
174-
</div>
171+
{IS_DEV && (
172+
<div>
173+
<span>
174+
<i>{element && element.id}</i> - {element && element.type}
175+
</span>
176+
</div>
177+
)}
175178
</>
176179
)
177180
}

apps/webapp/src/Editor/Components/MultiCombobox/useMultiComboboxChange.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback } from 'react'
22
import { OnChange, usePlateEditorRef } from '@udecode/plate'
33

4-
import { getTimeInText, isReservedOrClash, toLocaleString, withoutContinuousDelimiter } from '@mexit/core'
4+
import { getTimeInText, isReservedOrClash, mog, toLocaleString, withoutContinuousDelimiter } from '@mexit/core'
55

66
import { useLinks } from '../../../Hooks/useLinks'
77
import { useRouting } from '../../../Hooks/useRouting'
@@ -71,14 +71,17 @@ const useMultiComboboxOnChange = (editorId: string, keys: Record<string, Combobo
7171
const data = ct.data
7272

7373
if (!data) return false
74+
7475
const textAfterTrigger = search.textAfterTrigger
7576

7677
if (params.snippetid && textAfterTrigger?.startsWith('.')) return
7778

7879
const { isChild, key: pathKey } = withoutContinuousDelimiter(textAfterTrigger)
7980
const searchTerm = isChild ? `${getPathFromNodeid(editorId)}${pathKey}` : pathKey
8081

81-
const searchItems = fuzzySearch(data, searchTerm, (item) => item.text)
82+
mog('data', { data })
83+
84+
const searchItems = fuzzySearch(data, searchTerm || '', (item) => item?.text || '')
8285

8386
const { isExtended, extendedCommands } = getCommandExtended(search.textAfterTrigger, keys)
8487

0 commit comments

Comments
 (0)